logstash-integration-kafka 10.9.0-java → 11.3.2-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 +39 -2
- data/DEVELOPER.md +1 -1
- data/docs/index.asciidoc +1 -1
- data/docs/input-kafka.asciidoc +96 -8
- data/docs/output-kafka.asciidoc +20 -5
- data/lib/logstash/inputs/kafka.rb +42 -21
- data/lib/logstash/outputs/kafka.rb +7 -12
- data/lib/logstash/plugin_mixins/kafka/avro_schema_registry.rb +139 -0
- data/lib/logstash/plugin_mixins/kafka/common.rb +55 -0
- data/lib/logstash-integration-kafka_jars.rb +9 -14
- data/logstash-integration-kafka.gemspec +2 -2
- data/spec/integration/inputs/kafka_spec.rb +184 -20
- data/spec/integration/outputs/kafka_spec.rb +21 -1
- data/spec/unit/inputs/kafka_spec.rb +28 -5
- data/spec/unit/outputs/kafka_spec.rb +8 -0
- data/vendor/jar-dependencies/com/github/luben/zstd-jni/1.5.5-4/zstd-jni-1.5.5-4.jar +0 -0
- data/vendor/jar-dependencies/io/confluent/kafka-avro-serializer/7.4.0/kafka-avro-serializer-7.4.0.jar +0 -0
- data/vendor/jar-dependencies/io/confluent/kafka-schema-registry-client/7.4.0/kafka-schema-registry-client-7.4.0.jar +0 -0
- data/vendor/jar-dependencies/io/confluent/kafka-schema-serializer/7.4.0/kafka-schema-serializer-7.4.0.jar +0 -0
- data/vendor/jar-dependencies/org/apache/avro/avro/1.11.3/avro-1.11.3.jar +0 -0
- data/vendor/jar-dependencies/org/apache/kafka/kafka-clients/3.4.1/kafka-clients-3.4.1.jar +0 -0
- data/vendor/jar-dependencies/org/lz4/lz4-java/1.8.0/lz4-java-1.8.0.jar +0 -0
- data/vendor/jar-dependencies/org/slf4j/slf4j-api/{1.7.30/slf4j-api-1.7.30.jar → 1.7.36/slf4j-api-1.7.36.jar} +0 -0
- data/vendor/jar-dependencies/org/xerial/snappy/snappy-java/1.1.10.5/snappy-java-1.1.10.5.jar +0 -0
- metadata +16 -21
- data/lib/logstash/plugin_mixins/common.rb +0 -107
- data/lib/logstash/plugin_mixins/kafka_support.rb +0 -29
- data/vendor/jar-dependencies/com/github/luben/zstd-jni/1.4.4-7/zstd-jni-1.4.4-7.jar +0 -0
- data/vendor/jar-dependencies/io/confluent/common-config/5.5.1/common-config-5.5.1.jar +0 -0
- data/vendor/jar-dependencies/io/confluent/common-utils/5.5.1/common-utils-5.5.1.jar +0 -0
- data/vendor/jar-dependencies/io/confluent/kafka-avro-serializer/5.5.1/kafka-avro-serializer-5.5.1.jar +0 -0
- data/vendor/jar-dependencies/io/confluent/kafka-schema-registry-client/5.5.1/kafka-schema-registry-client-5.5.1.jar +0 -0
- data/vendor/jar-dependencies/io/confluent/kafka-schema-serializer/5.5.1/kafka-schema-serializer-5.5.1.jar +0 -0
- data/vendor/jar-dependencies/javax/ws/rs/javax.ws.rs-api/2.1.1/javax.ws.rs-api-2.1.1.jar +0 -0
- data/vendor/jar-dependencies/org/apache/avro/avro/1.9.2/avro-1.9.2.jar +0 -0
- data/vendor/jar-dependencies/org/apache/kafka/kafka-clients/2.5.1/kafka-clients-2.5.1.jar +0 -0
- data/vendor/jar-dependencies/org/apache/kafka/kafka_2.12/2.5.1/kafka_2.12-2.5.1.jar +0 -0
- data/vendor/jar-dependencies/org/glassfish/jersey/core/jersey-common/2.33/jersey-common-2.33.jar +0 -0
- data/vendor/jar-dependencies/org/lz4/lz4-java/1.7.1/lz4-java-1.7.1.jar +0 -0
- data/vendor/jar-dependencies/org/xerial/snappy/snappy-java/1.1.7.3/snappy-java-1.1.7.3.jar +0 -0
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'manticore'
|
2
|
+
|
3
|
+
module LogStash module PluginMixins module Kafka
|
4
|
+
module AvroSchemaRegistry
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
base.extend(self)
|
8
|
+
base.setup_schema_registry_config
|
9
|
+
end
|
10
|
+
|
11
|
+
def setup_schema_registry_config
|
12
|
+
# Option to set key to access Schema Registry.
|
13
|
+
config :schema_registry_key, :validate => :string
|
14
|
+
|
15
|
+
# Option to set secret to access Schema Registry.
|
16
|
+
config :schema_registry_secret, :validate => :password
|
17
|
+
|
18
|
+
# Option to set the endpoint of the Schema Registry.
|
19
|
+
# This option permit the usage of Avro Kafka deserializer which retrieve the schema of the Avro message from an
|
20
|
+
# instance of schema registry. If this option has value `value_deserializer_class` nor `topics_pattern` could be valued
|
21
|
+
config :schema_registry_url, :validate => :uri
|
22
|
+
|
23
|
+
# Option to set the proxy of the Schema Registry.
|
24
|
+
# This option permits to define a proxy to be used to reach the schema registry service instance.
|
25
|
+
config :schema_registry_proxy, :validate => :uri
|
26
|
+
|
27
|
+
# If schema registry client authentication is required, this setting stores the keystore path.
|
28
|
+
config :schema_registry_ssl_keystore_location, :validate => :string
|
29
|
+
|
30
|
+
# The keystore password.
|
31
|
+
config :schema_registry_ssl_keystore_password, :validate => :password
|
32
|
+
|
33
|
+
# The keystore type
|
34
|
+
config :schema_registry_ssl_keystore_type, :validate => ['jks', 'PKCS12'], :default => "jks"
|
35
|
+
|
36
|
+
# The JKS truststore path to validate the Schema Registry's certificate.
|
37
|
+
config :schema_registry_ssl_truststore_location, :validate => :string
|
38
|
+
|
39
|
+
# The truststore password.
|
40
|
+
config :schema_registry_ssl_truststore_password, :validate => :password
|
41
|
+
|
42
|
+
# The truststore type
|
43
|
+
config :schema_registry_ssl_truststore_type, :validate => ['jks', 'PKCS12'], :default => "jks"
|
44
|
+
|
45
|
+
# Option to skip validating the schema registry during registration. This can be useful when using
|
46
|
+
# certificate based auth
|
47
|
+
config :schema_registry_validation, :validate => ['auto', 'skip'], :default => 'auto'
|
48
|
+
end
|
49
|
+
|
50
|
+
def check_schema_registry_parameters
|
51
|
+
if @schema_registry_url
|
52
|
+
check_for_schema_registry_conflicts
|
53
|
+
@schema_registry_proxy_host, @schema_registry_proxy_port = split_proxy_into_host_and_port(schema_registry_proxy)
|
54
|
+
check_for_key_and_secret
|
55
|
+
check_for_schema_registry_connectivity_and_subjects if schema_registry_validation?
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def schema_registry_validation?
|
60
|
+
return false if schema_registry_validation.to_s == 'skip'
|
61
|
+
return false if using_kerberos? # pre-validation doesn't support kerberos
|
62
|
+
|
63
|
+
true
|
64
|
+
end
|
65
|
+
|
66
|
+
def using_kerberos?
|
67
|
+
security_protocol == "SASL_PLAINTEXT" || security_protocol == "SASL_SSL"
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
def check_for_schema_registry_conflicts
|
72
|
+
if @value_deserializer_class != LogStash::Inputs::Kafka::DEFAULT_DESERIALIZER_CLASS
|
73
|
+
raise LogStash::ConfigurationError, 'Option schema_registry_url prohibit the customization of value_deserializer_class'
|
74
|
+
end
|
75
|
+
if @topics_pattern && !@topics_pattern.empty?
|
76
|
+
raise LogStash::ConfigurationError, 'Option schema_registry_url prohibit the customization of topics_pattern'
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
def check_for_schema_registry_connectivity_and_subjects
|
82
|
+
options = {}
|
83
|
+
if schema_registry_proxy && !schema_registry_proxy.empty?
|
84
|
+
options[:proxy] = schema_registry_proxy.to_s
|
85
|
+
end
|
86
|
+
if schema_registry_key and !schema_registry_key.empty?
|
87
|
+
options[:auth] = {:user => schema_registry_key, :password => schema_registry_secret.value}
|
88
|
+
end
|
89
|
+
if schema_registry_ssl_truststore_location and !schema_registry_ssl_truststore_location.empty?
|
90
|
+
options[:ssl] = {} unless options.key?(:ssl)
|
91
|
+
options[:ssl][:truststore] = schema_registry_ssl_truststore_location unless schema_registry_ssl_truststore_location.nil?
|
92
|
+
options[:ssl][:truststore_password] = schema_registry_ssl_truststore_password.value unless schema_registry_ssl_truststore_password.nil?
|
93
|
+
options[:ssl][:truststore_type] = schema_registry_ssl_truststore_type unless schema_registry_ssl_truststore_type.nil?
|
94
|
+
end
|
95
|
+
if schema_registry_ssl_keystore_location and !schema_registry_ssl_keystore_location.empty?
|
96
|
+
options[:ssl] = {} unless options.key? :ssl
|
97
|
+
options[:ssl][:keystore] = schema_registry_ssl_keystore_location unless schema_registry_ssl_keystore_location.nil?
|
98
|
+
options[:ssl][:keystore_password] = schema_registry_ssl_keystore_password.value unless schema_registry_ssl_keystore_password.nil?
|
99
|
+
options[:ssl][:keystore_type] = schema_registry_ssl_keystore_type unless schema_registry_ssl_keystore_type.nil?
|
100
|
+
end
|
101
|
+
|
102
|
+
client = Manticore::Client.new(options)
|
103
|
+
begin
|
104
|
+
response = client.get(@schema_registry_url.uri.to_s + '/subjects').body
|
105
|
+
rescue Manticore::ManticoreException => e
|
106
|
+
raise LogStash::ConfigurationError.new("Schema registry service doesn't respond, error: #{e.message}")
|
107
|
+
end
|
108
|
+
registered_subjects = JSON.parse response
|
109
|
+
expected_subjects = @topics.map { |t| "#{t}-value"}
|
110
|
+
if (expected_subjects & registered_subjects).size != expected_subjects.size
|
111
|
+
undefined_topic_subjects = expected_subjects - registered_subjects
|
112
|
+
raise LogStash::ConfigurationError, "The schema registry does not contain definitions for required topic subjects: #{undefined_topic_subjects}"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def split_proxy_into_host_and_port(proxy_uri)
|
117
|
+
return nil unless proxy_uri && !proxy_uri.empty?
|
118
|
+
|
119
|
+
port = proxy_uri.port
|
120
|
+
|
121
|
+
host_spec = ""
|
122
|
+
host_spec << proxy_uri.scheme || "http"
|
123
|
+
host_spec << "://"
|
124
|
+
host_spec << "#{proxy_uri.userinfo}@" if proxy_uri.userinfo
|
125
|
+
host_spec << proxy_uri.host
|
126
|
+
|
127
|
+
[host_spec, port]
|
128
|
+
end
|
129
|
+
|
130
|
+
def check_for_key_and_secret
|
131
|
+
if schema_registry_key and !schema_registry_key.empty?
|
132
|
+
if !schema_registry_secret or schema_registry_secret.value.empty?
|
133
|
+
raise LogStash::ConfigurationError, "Setting `schema_registry_secret` is required when `schema_registry_key` is provided."
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
end end end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module LogStash module PluginMixins module Kafka
|
2
|
+
module Common
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
# COMMON CONFIGURATION SUPPORTED BY BOTH PRODUCER/CONSUMER
|
6
|
+
|
7
|
+
# Close idle connections after the number of milliseconds specified by this config.
|
8
|
+
base.config :connections_max_idle_ms, :validate => :number, :default => 540_000 # (9m) Kafka default
|
9
|
+
|
10
|
+
# The period of time in milliseconds after which we force a refresh of metadata even if
|
11
|
+
# we haven't seen any partition leadership changes to proactively discover any new brokers or partitions
|
12
|
+
base.config :metadata_max_age_ms, :validate => :number, :default => 300_000 # (5m) Kafka default
|
13
|
+
|
14
|
+
# The configuration controls the maximum amount of time the client will wait for the response of a request.
|
15
|
+
# If the response is not received before the timeout elapses the client will resend the request if necessary
|
16
|
+
# or fail the request if retries are exhausted.
|
17
|
+
base.config :request_timeout_ms, :validate => :number, :default => 40_000 # Kafka default
|
18
|
+
end
|
19
|
+
|
20
|
+
def set_trustore_keystore_config(props)
|
21
|
+
props.put("ssl.truststore.type", ssl_truststore_type) unless ssl_truststore_type.nil?
|
22
|
+
props.put("ssl.truststore.location", ssl_truststore_location) unless ssl_truststore_location.nil?
|
23
|
+
props.put("ssl.truststore.password", ssl_truststore_password.value) unless ssl_truststore_password.nil?
|
24
|
+
|
25
|
+
# Client auth stuff
|
26
|
+
props.put("ssl.keystore.type", ssl_keystore_type) unless ssl_keystore_type.nil?
|
27
|
+
props.put("ssl.key.password", ssl_key_password.value) unless ssl_key_password.nil?
|
28
|
+
props.put("ssl.keystore.location", ssl_keystore_location) unless ssl_keystore_location.nil?
|
29
|
+
props.put("ssl.keystore.password", ssl_keystore_password.value) unless ssl_keystore_password.nil?
|
30
|
+
props.put("ssl.endpoint.identification.algorithm", ssl_endpoint_identification_algorithm) unless ssl_endpoint_identification_algorithm.nil?
|
31
|
+
end
|
32
|
+
|
33
|
+
def set_sasl_config(props)
|
34
|
+
java.lang.System.setProperty("java.security.auth.login.config", jaas_path) unless jaas_path.nil?
|
35
|
+
java.lang.System.setProperty("java.security.krb5.conf", kerberos_config) unless kerberos_config.nil?
|
36
|
+
|
37
|
+
props.put("sasl.mechanism", sasl_mechanism)
|
38
|
+
if sasl_mechanism == "GSSAPI" && sasl_kerberos_service_name.nil?
|
39
|
+
raise LogStash::ConfigurationError, "sasl_kerberos_service_name must be specified when SASL mechanism is GSSAPI"
|
40
|
+
end
|
41
|
+
|
42
|
+
props.put("sasl.kerberos.service.name", sasl_kerberos_service_name) unless sasl_kerberos_service_name.nil?
|
43
|
+
props.put("sasl.jaas.config", sasl_jaas_config) unless sasl_jaas_config.nil?
|
44
|
+
end
|
45
|
+
|
46
|
+
def reassign_dns_lookup
|
47
|
+
if @client_dns_lookup == "default"
|
48
|
+
@client_dns_lookup = "use_all_dns_ips"
|
49
|
+
logger.warn("client_dns_lookup setting 'default' value is deprecated, forced to 'use_all_dns_ips', please update your configuration")
|
50
|
+
deprecation_logger.deprecated("Deprecated value `default` for `client_dns_lookup` option; use `use_all_dns_ips` instead.")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end end end
|
@@ -1,17 +1,12 @@
|
|
1
1
|
# AUTOGENERATED BY THE GRADLE SCRIPT. DO NOT EDIT.
|
2
2
|
|
3
3
|
require 'jar_dependencies'
|
4
|
-
require_jar('io.confluent', 'kafka-avro-serializer', '
|
5
|
-
require_jar('io.confluent', 'kafka-schema-serializer', '
|
6
|
-
require_jar('
|
7
|
-
require_jar('
|
8
|
-
require_jar('
|
9
|
-
require_jar('org.
|
10
|
-
require_jar('
|
11
|
-
require_jar('
|
12
|
-
require_jar('org.
|
13
|
-
require_jar('org.apache.kafka', 'kafka-clients', '2.5.1')
|
14
|
-
require_jar('com.github.luben', 'zstd-jni', '1.4.4-7')
|
15
|
-
require_jar('org.slf4j', 'slf4j-api', '1.7.30')
|
16
|
-
require_jar('org.lz4', 'lz4-java', '1.7.1')
|
17
|
-
require_jar('org.xerial.snappy', 'snappy-java', '1.1.7.3')
|
4
|
+
require_jar('io.confluent', 'kafka-avro-serializer', '7.4.0')
|
5
|
+
require_jar('io.confluent', 'kafka-schema-serializer', '7.4.0')
|
6
|
+
require_jar('org.apache.avro', 'avro', '1.11.3')
|
7
|
+
require_jar('io.confluent', 'kafka-schema-registry-client', '7.4.0')
|
8
|
+
require_jar('org.apache.kafka', 'kafka-clients', '3.4.1')
|
9
|
+
require_jar('org.slf4j', 'slf4j-api', '1.7.36')
|
10
|
+
require_jar('com.github.luben', 'zstd-jni', '1.5.5-4')
|
11
|
+
require_jar('org.lz4', 'lz4-java', '1.8.0')
|
12
|
+
require_jar('org.xerial.snappy', 'snappy-java', '1.1.10.5')
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'logstash-integration-kafka'
|
3
|
-
s.version = '
|
3
|
+
s.version = '11.3.2'
|
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 "+
|
@@ -41,7 +41,7 @@ Gem::Specification.new do |s|
|
|
41
41
|
|
42
42
|
# Gem dependencies
|
43
43
|
s.add_runtime_dependency "logstash-core-plugin-api", ">= 1.60", "<= 2.99"
|
44
|
-
s.add_runtime_dependency "logstash-core", ">=
|
44
|
+
s.add_runtime_dependency "logstash-core", ">= 8.3.0"
|
45
45
|
|
46
46
|
s.add_runtime_dependency 'logstash-codec-json'
|
47
47
|
s.add_runtime_dependency 'logstash-codec-plain'
|
@@ -79,6 +79,7 @@ describe "inputs/kafka", :integration => true do
|
|
79
79
|
producer = org.apache.kafka.clients.producer.KafkaProducer.new(props)
|
80
80
|
|
81
81
|
producer.send(record)
|
82
|
+
producer.flush
|
82
83
|
producer.close
|
83
84
|
end
|
84
85
|
|
@@ -185,10 +186,105 @@ describe "inputs/kafka", :integration => true do
|
|
185
186
|
end
|
186
187
|
end
|
187
188
|
end
|
189
|
+
|
190
|
+
context "static membership 'group.instance.id' setting" do
|
191
|
+
let(:base_config) do
|
192
|
+
{
|
193
|
+
"topics" => ["logstash_integration_static_membership_topic"],
|
194
|
+
"group_id" => "logstash",
|
195
|
+
"consumer_threads" => 1,
|
196
|
+
# this is needed because the worker thread could be executed little after the producer sent the "up" message
|
197
|
+
"auto_offset_reset" => "earliest",
|
198
|
+
"group_instance_id" => "test_static_group_id"
|
199
|
+
}
|
200
|
+
end
|
201
|
+
let(:consumer_config) { base_config }
|
202
|
+
let(:logger) { double("logger") }
|
203
|
+
let(:queue) { java.util.concurrent.ArrayBlockingQueue.new(10) }
|
204
|
+
let(:kafka_input) { LogStash::Inputs::Kafka.new(consumer_config) }
|
205
|
+
before :each do
|
206
|
+
allow(LogStash::Inputs::Kafka).to receive(:logger).and_return(logger)
|
207
|
+
[:error, :warn, :info, :debug].each do |level|
|
208
|
+
allow(logger).to receive(level)
|
209
|
+
end
|
210
|
+
|
211
|
+
kafka_input.register
|
212
|
+
end
|
213
|
+
|
214
|
+
it "input plugin disconnects from the broker when another client with same static membership connects" do
|
215
|
+
expect(logger).to receive(:error).with("Another consumer with same group.instance.id has connected", anything)
|
216
|
+
|
217
|
+
input_worker = java.lang.Thread.new { kafka_input.run(queue) }
|
218
|
+
begin
|
219
|
+
input_worker.start
|
220
|
+
wait_kafka_input_is_ready("logstash_integration_static_membership_topic", queue)
|
221
|
+
saboteur_kafka_consumer = create_consumer_and_start_consuming("test_static_group_id")
|
222
|
+
saboteur_kafka_consumer.run # ask to be scheduled
|
223
|
+
saboteur_kafka_consumer.join
|
224
|
+
|
225
|
+
expect(saboteur_kafka_consumer.value).to eq("saboteur exited")
|
226
|
+
ensure
|
227
|
+
input_worker.join(30_000)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
context "when the plugin is configured with multiple consumer threads" do
|
232
|
+
let(:consumer_config) { base_config.merge({"consumer_threads" => 2}) }
|
233
|
+
|
234
|
+
it "should avoid to connect with same 'group.instance.id'" do
|
235
|
+
expect(logger).to_not receive(:error).with("Another consumer with same group.instance.id has connected", anything)
|
236
|
+
|
237
|
+
input_worker = java.lang.Thread.new { kafka_input.run(queue) }
|
238
|
+
begin
|
239
|
+
input_worker.start
|
240
|
+
wait_kafka_input_is_ready("logstash_integration_static_membership_topic", queue)
|
241
|
+
ensure
|
242
|
+
kafka_input.stop
|
243
|
+
input_worker.join(1_000)
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
# return consumer Ruby Thread
|
251
|
+
def create_consumer_and_start_consuming(static_group_id)
|
252
|
+
props = java.util.Properties.new
|
253
|
+
kafka = org.apache.kafka.clients.consumer.ConsumerConfig
|
254
|
+
props.put(kafka::BOOTSTRAP_SERVERS_CONFIG, "localhost:9092")
|
255
|
+
props.put(kafka::KEY_DESERIALIZER_CLASS_CONFIG, LogStash::Inputs::Kafka::DEFAULT_DESERIALIZER_CLASS)
|
256
|
+
props.put(kafka::VALUE_DESERIALIZER_CLASS_CONFIG, LogStash::Inputs::Kafka::DEFAULT_DESERIALIZER_CLASS)
|
257
|
+
props.put(kafka::GROUP_ID_CONFIG, "logstash")
|
258
|
+
props.put(kafka::GROUP_INSTANCE_ID_CONFIG, static_group_id)
|
259
|
+
consumer = org.apache.kafka.clients.consumer.KafkaConsumer.new(props)
|
260
|
+
|
261
|
+
Thread.new do
|
262
|
+
LogStash::Util::set_thread_name("integration_test_simple_consumer")
|
263
|
+
begin
|
264
|
+
consumer.subscribe(["logstash_integration_static_membership_topic"])
|
265
|
+
records = consumer.poll(java.time.Duration.ofSeconds(3))
|
266
|
+
"saboteur exited"
|
267
|
+
rescue => e
|
268
|
+
e # return the exception reached in thread.value
|
269
|
+
ensure
|
270
|
+
consumer.close
|
271
|
+
end
|
272
|
+
end
|
188
273
|
end
|
189
274
|
|
190
275
|
private
|
191
276
|
|
277
|
+
def wait_kafka_input_is_ready(topic, queue)
|
278
|
+
# this is needed to give time to the kafka input to be up and running
|
279
|
+
header = org.apache.kafka.common.header.internals.RecordHeader.new("name", "Ping Up".to_java_bytes)
|
280
|
+
record = org.apache.kafka.clients.producer.ProducerRecord.new(topic, 0, "key", "value", [header])
|
281
|
+
send_message(record)
|
282
|
+
|
283
|
+
# Wait the message is processed
|
284
|
+
message = queue.poll(1, java.util.concurrent.TimeUnit::MINUTES)
|
285
|
+
expect(message).to_not eq(nil)
|
286
|
+
end
|
287
|
+
|
192
288
|
def consume_messages(config, queue: Queue.new, timeout:, event_count:)
|
193
289
|
kafka_input = LogStash::Inputs::Kafka.new(config)
|
194
290
|
kafka_input.register
|
@@ -257,10 +353,13 @@ describe "schema registry connection options" do
|
|
257
353
|
end
|
258
354
|
end
|
259
355
|
|
260
|
-
def save_avro_schema_to_schema_registry(schema_file, subject_name)
|
356
|
+
def save_avro_schema_to_schema_registry(schema_file, subject_name, proto = 'http', port = 8081, manticore_options = {})
|
261
357
|
raw_schema = File.readlines(schema_file).map(&:chomp).join
|
262
358
|
raw_schema_quoted = raw_schema.gsub('"', '\"')
|
263
|
-
|
359
|
+
|
360
|
+
client = Manticore::Client.new(manticore_options)
|
361
|
+
|
362
|
+
response = client.post("#{proto}://localhost:#{port}/subjects/#{subject_name}/versions",
|
264
363
|
body: '{"schema": "' + raw_schema_quoted + '"}',
|
265
364
|
headers: {"Content-Type" => "application/vnd.schemaregistry.v1+json"})
|
266
365
|
response
|
@@ -282,8 +381,17 @@ def startup_schema_registry(schema_registry, auth=false)
|
|
282
381
|
end
|
283
382
|
end
|
284
383
|
|
285
|
-
|
286
|
-
|
384
|
+
shared_examples 'it has endpoints available to' do |tls|
|
385
|
+
let(:port) { tls ? 8083 : 8081 }
|
386
|
+
let(:proto) { tls ? 'https' : 'http' }
|
387
|
+
|
388
|
+
manticore_options = {
|
389
|
+
:ssl => {
|
390
|
+
:truststore => File.join(Dir.pwd, "tls_repository/clienttruststore.jks"),
|
391
|
+
:truststore_password => "changeit"
|
392
|
+
}
|
393
|
+
}
|
394
|
+
schema_registry = Manticore::Client.new(manticore_options)
|
287
395
|
|
288
396
|
before(:all) do
|
289
397
|
startup_schema_registry(schema_registry)
|
@@ -295,36 +403,53 @@ describe "Schema registry API", :integration => true do
|
|
295
403
|
|
296
404
|
context 'listing subject on clean instance' do
|
297
405
|
it "should return an empty set" do
|
298
|
-
subjects = JSON.parse schema_registry.get(
|
406
|
+
subjects = JSON.parse schema_registry.get("#{proto}://localhost:#{port}/subjects").body
|
299
407
|
expect( subjects ).to be_empty
|
300
408
|
end
|
301
409
|
end
|
302
410
|
|
303
411
|
context 'send a schema definition' do
|
304
412
|
it "save the definition" do
|
305
|
-
response = save_avro_schema_to_schema_registry(File.join(Dir.pwd, "spec", "unit", "inputs", "avro_schema_fixture_payment.asvc"), "schema_test_1")
|
413
|
+
response = save_avro_schema_to_schema_registry(File.join(Dir.pwd, "spec", "unit", "inputs", "avro_schema_fixture_payment.asvc"), "schema_test_1", proto, port, manticore_options)
|
306
414
|
expect( response.code ).to be(200)
|
307
415
|
delete_remote_schema(schema_registry, "schema_test_1")
|
308
416
|
end
|
309
417
|
|
310
418
|
it "delete the schema just added" do
|
311
|
-
response = save_avro_schema_to_schema_registry(File.join(Dir.pwd, "spec", "unit", "inputs", "avro_schema_fixture_payment.asvc"), "schema_test_1")
|
419
|
+
response = save_avro_schema_to_schema_registry(File.join(Dir.pwd, "spec", "unit", "inputs", "avro_schema_fixture_payment.asvc"), "schema_test_1", proto, port, manticore_options)
|
312
420
|
expect( response.code ).to be(200)
|
313
421
|
|
314
|
-
expect( schema_registry.delete(
|
422
|
+
expect( schema_registry.delete("#{proto}://localhost:#{port}/subjects/schema_test_1?permanent=false").code ).to be(200)
|
315
423
|
sleep(1)
|
316
|
-
subjects = JSON.parse schema_registry.get(
|
424
|
+
subjects = JSON.parse schema_registry.get("#{proto}://localhost:#{port}/subjects").body
|
317
425
|
expect( subjects ).to be_empty
|
318
426
|
end
|
319
427
|
end
|
320
428
|
end
|
321
429
|
|
430
|
+
describe "Schema registry API", :integration => true do
|
431
|
+
|
432
|
+
context "when exposed with HTTPS" do
|
433
|
+
it_behaves_like 'it has endpoints available to', true
|
434
|
+
end
|
435
|
+
|
436
|
+
context "when exposed with plain HTTP" do
|
437
|
+
it_behaves_like 'it has endpoints available to', false
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
322
441
|
def shutdown_schema_registry
|
323
442
|
system('./stop_schema_registry.sh')
|
324
443
|
end
|
325
444
|
|
326
445
|
describe "Deserializing with the schema registry", :integration => true do
|
327
|
-
|
446
|
+
manticore_options = {
|
447
|
+
:ssl => {
|
448
|
+
:truststore => File.join(Dir.pwd, "tls_repository/clienttruststore.jks"),
|
449
|
+
:truststore_password => "changeit"
|
450
|
+
}
|
451
|
+
}
|
452
|
+
schema_registry = Manticore::Client.new(manticore_options)
|
328
453
|
|
329
454
|
shared_examples 'it reads from a topic using a schema registry' do |with_auth|
|
330
455
|
|
@@ -423,28 +548,57 @@ describe "Deserializing with the schema registry", :integration => true do
|
|
423
548
|
end
|
424
549
|
end
|
425
550
|
|
426
|
-
|
551
|
+
shared_examples 'with an unauthed schema registry' do |tls|
|
552
|
+
let(:port) { tls ? 8083 : 8081 }
|
553
|
+
let(:proto) { tls ? 'https' : 'http' }
|
554
|
+
|
427
555
|
let(:auth) { false }
|
428
556
|
let(:avro_topic_name) { "topic_avro" }
|
429
|
-
let(:subject_url) { "
|
430
|
-
let(:plain_config) { base_config.merge!({
|
557
|
+
let(:subject_url) { "#{proto}://localhost:#{port}/subjects" }
|
558
|
+
let(:plain_config) { base_config.merge!({
|
559
|
+
'schema_registry_url' => "#{proto}://localhost:#{port}",
|
560
|
+
'schema_registry_ssl_truststore_location' => File.join(Dir.pwd, "tls_repository/clienttruststore.jks"),
|
561
|
+
'schema_registry_ssl_truststore_password' => 'changeit',
|
562
|
+
}) }
|
431
563
|
|
432
564
|
it_behaves_like 'it reads from a topic using a schema registry', false
|
433
565
|
end
|
434
566
|
|
435
|
-
context 'with an
|
567
|
+
context 'with an unauthed schema registry' do
|
568
|
+
context "accessed through HTTPS" do
|
569
|
+
it_behaves_like 'with an unauthed schema registry', true
|
570
|
+
end
|
571
|
+
|
572
|
+
context "accessed through HTTPS" do
|
573
|
+
it_behaves_like 'with an unauthed schema registry', false
|
574
|
+
end
|
575
|
+
end
|
576
|
+
|
577
|
+
shared_examples 'with an authed schema registry' do |tls|
|
578
|
+
let(:port) { tls ? 8083 : 8081 }
|
579
|
+
let(:proto) { tls ? 'https' : 'http' }
|
436
580
|
let(:auth) { true }
|
437
581
|
let(:user) { "barney" }
|
438
582
|
let(:password) { "changeme" }
|
439
583
|
let(:avro_topic_name) { "topic_avro_auth" }
|
440
|
-
let(:subject_url) { "
|
584
|
+
let(:subject_url) { "#{proto}://#{user}:#{password}@localhost:#{port}/subjects" }
|
585
|
+
let(:tls_base_config) do
|
586
|
+
if tls
|
587
|
+
base_config.merge({
|
588
|
+
'schema_registry_ssl_truststore_location' => ::File.join(Dir.pwd, "tls_repository/clienttruststore.jks"),
|
589
|
+
'schema_registry_ssl_truststore_password' => 'changeit',
|
590
|
+
})
|
591
|
+
else
|
592
|
+
base_config
|
593
|
+
end
|
594
|
+
end
|
441
595
|
|
442
596
|
context 'using schema_registry_key' do
|
443
597
|
let(:plain_config) do
|
444
|
-
|
445
|
-
'schema_registry_url' => "
|
598
|
+
tls_base_config.merge!({
|
599
|
+
'schema_registry_url' => "#{proto}://localhost:#{port}",
|
446
600
|
'schema_registry_key' => user,
|
447
|
-
'schema_registry_secret' => password
|
601
|
+
'schema_registry_secret' => password,
|
448
602
|
})
|
449
603
|
end
|
450
604
|
|
@@ -453,12 +607,22 @@ describe "Deserializing with the schema registry", :integration => true do
|
|
453
607
|
|
454
608
|
context 'using schema_registry_url' do
|
455
609
|
let(:plain_config) do
|
456
|
-
|
457
|
-
'schema_registry_url' => "
|
610
|
+
tls_base_config.merge!({
|
611
|
+
'schema_registry_url' => "#{proto}://#{user}:#{password}@localhost:#{port}",
|
458
612
|
})
|
459
613
|
end
|
460
614
|
|
461
615
|
it_behaves_like 'it reads from a topic using a schema registry', true
|
462
616
|
end
|
463
617
|
end
|
618
|
+
|
619
|
+
context 'with an authed schema registry' do
|
620
|
+
context "accessed through HTTPS" do
|
621
|
+
it_behaves_like 'with an authed schema registry', true
|
622
|
+
end
|
623
|
+
|
624
|
+
context "accessed through HTTPS" do
|
625
|
+
it_behaves_like 'with an authed schema registry', false
|
626
|
+
end
|
627
|
+
end
|
464
628
|
end
|
@@ -28,7 +28,8 @@ describe "outputs/kafka", :integration => true do
|
|
28
28
|
let(:num_events) { 3 }
|
29
29
|
|
30
30
|
before :each do
|
31
|
-
|
31
|
+
# NOTE: the connections_max_idle_ms is irrelevant just testing that configuration works ...
|
32
|
+
config = base_config.merge({"topic_id" => test_topic, "connections_max_idle_ms" => 540_000})
|
32
33
|
load_kafka_data(config)
|
33
34
|
end
|
34
35
|
|
@@ -139,6 +140,25 @@ describe "outputs/kafka", :integration => true do
|
|
139
140
|
# end
|
140
141
|
end
|
141
142
|
|
143
|
+
context 'when using zstd compression' do
|
144
|
+
let(:test_topic) { 'logstash_integration_zstd_topic' }
|
145
|
+
|
146
|
+
before :each do
|
147
|
+
config = base_config.merge({"topic_id" => test_topic, "compression_type" => "zstd"})
|
148
|
+
load_kafka_data(config)
|
149
|
+
end
|
150
|
+
|
151
|
+
# NOTE: depends on zstd-ruby gem which is using a C-extension
|
152
|
+
# it 'should have data integrity' do
|
153
|
+
# messages = fetch_messages(test_topic)
|
154
|
+
#
|
155
|
+
# expect(messages.size).to eq(num_events)
|
156
|
+
# messages.each do |m|
|
157
|
+
# expect(m.value).to eq(event.to_s)
|
158
|
+
# end
|
159
|
+
# end
|
160
|
+
end
|
161
|
+
|
142
162
|
context 'when using multi partition topic' do
|
143
163
|
let(:num_events) { 100 } # ~ more than (batch.size) 16,384 bytes
|
144
164
|
let(:test_topic) { 'logstash_integration_topic3' }
|
@@ -83,6 +83,16 @@ describe LogStash::Inputs::Kafka do
|
|
83
83
|
it "should register" do
|
84
84
|
expect { subject.register }.to_not raise_error
|
85
85
|
end
|
86
|
+
|
87
|
+
context "when the deprecated `default` is specified" do
|
88
|
+
let(:config) { common_config.merge('client_dns_lookup' => 'default') }
|
89
|
+
|
90
|
+
it 'should fallback `client_dns_lookup` to `use_all_dns_ips`' do
|
91
|
+
subject.register
|
92
|
+
|
93
|
+
expect(subject.client_dns_lookup).to eq('use_all_dns_ips')
|
94
|
+
end
|
95
|
+
end
|
86
96
|
end
|
87
97
|
|
88
98
|
describe '#running' do
|
@@ -277,6 +287,19 @@ describe LogStash::Inputs::Kafka do
|
|
277
287
|
subject.register
|
278
288
|
expect(subject.metadata_mode).to include(:record_props)
|
279
289
|
end
|
290
|
+
|
291
|
+
context "guards against nil header" do
|
292
|
+
let(:header) { double(:value => nil, :key => "k") }
|
293
|
+
let(:headers) { [ header ] }
|
294
|
+
let(:record) { double(:headers => headers, :topic => "topic", :partition => 0,
|
295
|
+
:offset => 123456789, :key => "someId", :timestamp => nil ) }
|
296
|
+
|
297
|
+
it "does not raise error when key is nil" do
|
298
|
+
subject.register
|
299
|
+
evt = LogStash::Event.new('message' => 'Hello')
|
300
|
+
expect { subject.maybe_set_metadata(evt, record) }.not_to raise_error
|
301
|
+
end
|
302
|
+
end
|
280
303
|
end
|
281
304
|
|
282
305
|
context 'with client_rack' do
|
@@ -287,7 +310,7 @@ describe LogStash::Inputs::Kafka do
|
|
287
310
|
to receive(:new).with(hash_including('client.rack' => 'EU-R1')).
|
288
311
|
and_return kafka_client = double('kafka-consumer')
|
289
312
|
|
290
|
-
expect( subject.send(:create_consumer, 'sample_client-0') ).to be kafka_client
|
313
|
+
expect( subject.send(:create_consumer, 'sample_client-0', 'group_instance_id') ).to be kafka_client
|
291
314
|
end
|
292
315
|
end
|
293
316
|
|
@@ -299,7 +322,7 @@ describe LogStash::Inputs::Kafka do
|
|
299
322
|
to receive(:new).with(hash_including('session.timeout.ms' => '25000', 'max.poll.interval.ms' => '345000')).
|
300
323
|
and_return kafka_client = double('kafka-consumer')
|
301
324
|
|
302
|
-
expect( subject.send(:create_consumer, 'sample_client-1') ).to be kafka_client
|
325
|
+
expect( subject.send(:create_consumer, 'sample_client-1', 'group_instance_id') ).to be kafka_client
|
303
326
|
end
|
304
327
|
end
|
305
328
|
|
@@ -311,7 +334,7 @@ describe LogStash::Inputs::Kafka do
|
|
311
334
|
to receive(:new).with(hash_including('session.timeout.ms' => '25200', 'max.poll.interval.ms' => '123000')).
|
312
335
|
and_return kafka_client = double('kafka-consumer')
|
313
336
|
|
314
|
-
expect( subject.send(:create_consumer, 'sample_client-2') ).to be kafka_client
|
337
|
+
expect( subject.send(:create_consumer, 'sample_client-2', 'group_instance_id') ).to be kafka_client
|
315
338
|
end
|
316
339
|
end
|
317
340
|
|
@@ -323,7 +346,7 @@ describe LogStash::Inputs::Kafka do
|
|
323
346
|
to receive(:new).with(hash_including('enable.auto.commit' => 'false', 'check.crcs' => 'true')).
|
324
347
|
and_return kafka_client = double('kafka-consumer')
|
325
348
|
|
326
|
-
expect( subject.send(:create_consumer, 'sample_client-3') ).to be kafka_client
|
349
|
+
expect( subject.send(:create_consumer, 'sample_client-3', 'group_instance_id') ).to be kafka_client
|
327
350
|
expect( subject.enable_auto_commit ).to be false
|
328
351
|
end
|
329
352
|
end
|
@@ -336,7 +359,7 @@ describe LogStash::Inputs::Kafka do
|
|
336
359
|
to receive(:new).with(hash_including('enable.auto.commit' => 'true', 'check.crcs' => 'false')).
|
337
360
|
and_return kafka_client = double('kafka-consumer')
|
338
361
|
|
339
|
-
expect( subject.send(:create_consumer, 'sample_client-4') ).to be kafka_client
|
362
|
+
expect( subject.send(:create_consumer, 'sample_client-4', 'group_instance_id') ).to be kafka_client
|
340
363
|
expect( subject.enable_auto_commit ).to be true
|
341
364
|
end
|
342
365
|
end
|