rdkafka 0.8.0 → 0.11.0
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/.rspec +1 -0
- data/.semaphore/semaphore.yml +23 -0
- data/CHANGELOG.md +24 -1
- data/Guardfile +19 -0
- data/README.md +8 -3
- data/bin/console +11 -0
- data/docker-compose.yml +5 -3
- data/ext/README.md +8 -1
- data/ext/Rakefile +5 -20
- data/lib/rdkafka/abstract_handle.rb +82 -0
- data/lib/rdkafka/admin/create_topic_handle.rb +27 -0
- data/lib/rdkafka/admin/create_topic_report.rb +22 -0
- data/lib/rdkafka/admin/delete_topic_handle.rb +27 -0
- data/lib/rdkafka/admin/delete_topic_report.rb +22 -0
- data/lib/rdkafka/admin.rb +155 -0
- data/lib/rdkafka/bindings.rb +57 -18
- data/lib/rdkafka/callbacks.rb +106 -0
- data/lib/rdkafka/config.rb +59 -3
- data/lib/rdkafka/consumer.rb +125 -5
- data/lib/rdkafka/error.rb +29 -3
- data/lib/rdkafka/metadata.rb +6 -5
- data/lib/rdkafka/producer/delivery_handle.rb +7 -53
- data/lib/rdkafka/producer/delivery_report.rb +1 -1
- data/lib/rdkafka/producer.rb +27 -12
- data/lib/rdkafka/version.rb +3 -3
- data/lib/rdkafka.rb +7 -0
- data/rdkafka.gemspec +9 -7
- data/spec/rdkafka/abstract_handle_spec.rb +113 -0
- data/spec/rdkafka/admin/create_topic_handle_spec.rb +52 -0
- data/spec/rdkafka/admin/create_topic_report_spec.rb +16 -0
- data/spec/rdkafka/admin/delete_topic_handle_spec.rb +52 -0
- data/spec/rdkafka/admin/delete_topic_report_spec.rb +16 -0
- data/spec/rdkafka/admin_spec.rb +203 -0
- data/spec/rdkafka/bindings_spec.rb +32 -8
- data/spec/rdkafka/callbacks_spec.rb +20 -0
- data/spec/rdkafka/config_spec.rb +78 -9
- data/spec/rdkafka/consumer_spec.rb +326 -42
- data/spec/rdkafka/error_spec.rb +4 -0
- data/spec/rdkafka/metadata_spec.rb +78 -0
- data/spec/rdkafka/producer/delivery_handle_spec.rb +1 -41
- data/spec/rdkafka/producer_spec.rb +102 -34
- data/spec/spec_helper.rb +78 -20
- metadata +84 -29
- data/.travis.yml +0 -48
data/lib/rdkafka/producer.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require "securerandom"
|
2
|
+
|
1
3
|
module Rdkafka
|
2
4
|
# A producer for Kafka messages. To create a producer set up a {Config} and call {Config#producer producer} on that.
|
3
5
|
class Producer
|
@@ -9,8 +11,13 @@ module Rdkafka
|
|
9
11
|
|
10
12
|
# @private
|
11
13
|
def initialize(native_kafka)
|
14
|
+
@id = SecureRandom.uuid
|
12
15
|
@closing = false
|
13
16
|
@native_kafka = native_kafka
|
17
|
+
|
18
|
+
# Makes sure, that the producer gets closed before it gets GCed by Ruby
|
19
|
+
ObjectSpace.define_finalizer(@id, proc { close })
|
20
|
+
|
14
21
|
# Start thread to poll client for delivery callbacks
|
15
22
|
@polling_thread = Thread.new do
|
16
23
|
loop do
|
@@ -27,16 +34,18 @@ module Rdkafka
|
|
27
34
|
# Set a callback that will be called every time a message is successfully produced.
|
28
35
|
# The callback is called with a {DeliveryReport}
|
29
36
|
#
|
30
|
-
# @param callback [Proc] The callback
|
37
|
+
# @param callback [Proc, #call] The callback
|
31
38
|
#
|
32
39
|
# @return [nil]
|
33
40
|
def delivery_callback=(callback)
|
34
|
-
raise TypeError.new("Callback has to be
|
41
|
+
raise TypeError.new("Callback has to be callable") unless callback.respond_to?(:call)
|
35
42
|
@delivery_callback = callback
|
36
43
|
end
|
37
44
|
|
38
45
|
# Close this producer and wait for the internal poll queue to empty.
|
39
46
|
def close
|
47
|
+
ObjectSpace.undefine_finalizer(@id)
|
48
|
+
|
40
49
|
return unless @native_kafka
|
41
50
|
|
42
51
|
# Indicate to polling thread that we're closing
|
@@ -55,7 +64,8 @@ module Rdkafka
|
|
55
64
|
# @return partition count [Integer,nil]
|
56
65
|
#
|
57
66
|
def partition_count(topic)
|
58
|
-
|
67
|
+
closed_producer_check(__method__)
|
68
|
+
Rdkafka::Metadata.new(@native_kafka, topic).topics&.first[:partition_count]
|
59
69
|
end
|
60
70
|
|
61
71
|
# Produces a message to a Kafka topic. The message is added to rdkafka's queue, call {DeliveryHandle#wait wait} on the returned delivery handle to make sure it is delivered.
|
@@ -65,8 +75,9 @@ module Rdkafka
|
|
65
75
|
#
|
66
76
|
# @param topic [String] The topic to produce to
|
67
77
|
# @param payload [String,nil] The message's payload
|
68
|
-
# @param key [String] The message's key
|
78
|
+
# @param key [String, nil] The message's key
|
69
79
|
# @param partition [Integer,nil] Optional partition to produce to
|
80
|
+
# @param partition_key [String, nil] Optional partition key based on which partition assignment can happen
|
70
81
|
# @param timestamp [Time,Integer,nil] Optional timestamp of this message. Integer timestamp is in milliseconds since Jan 1 1970.
|
71
82
|
# @param headers [Hash<String,String>] Optional message headers
|
72
83
|
#
|
@@ -74,6 +85,8 @@ module Rdkafka
|
|
74
85
|
#
|
75
86
|
# @return [DeliveryHandle] Delivery handle that can be used to wait for the result of producing this message
|
76
87
|
def produce(topic:, payload: nil, key: nil, partition: nil, partition_key: nil, timestamp: nil, headers: nil)
|
88
|
+
closed_producer_check(__method__)
|
89
|
+
|
77
90
|
# Start by checking and converting the input
|
78
91
|
|
79
92
|
# Get payload length
|
@@ -117,7 +130,7 @@ module Rdkafka
|
|
117
130
|
delivery_handle[:response] = -1
|
118
131
|
delivery_handle[:partition] = -1
|
119
132
|
delivery_handle[:offset] = -1
|
120
|
-
DeliveryHandle.register(delivery_handle
|
133
|
+
DeliveryHandle.register(delivery_handle)
|
121
134
|
|
122
135
|
args = [
|
123
136
|
:int, Rdkafka::Bindings::RD_KAFKA_VTYPE_TOPIC, :string, topic,
|
@@ -133,16 +146,14 @@ module Rdkafka
|
|
133
146
|
headers.each do |key0, value0|
|
134
147
|
key = key0.to_s
|
135
148
|
value = value0.to_s
|
136
|
-
args
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
:size_t, value.bytes.size
|
141
|
-
]
|
149
|
+
args << :int << Rdkafka::Bindings::RD_KAFKA_VTYPE_HEADER
|
150
|
+
args << :string << key
|
151
|
+
args << :pointer << value
|
152
|
+
args << :size_t << value.bytes.size
|
142
153
|
end
|
143
154
|
end
|
144
155
|
|
145
|
-
args
|
156
|
+
args << :int << Rdkafka::Bindings::RD_KAFKA_VTYPE_END
|
146
157
|
|
147
158
|
# Produce the message
|
148
159
|
response = Rdkafka::Bindings.rd_kafka_producev(
|
@@ -163,5 +174,9 @@ module Rdkafka
|
|
163
174
|
def call_delivery_callback(delivery_handle)
|
164
175
|
@delivery_callback.call(delivery_handle) if @delivery_callback
|
165
176
|
end
|
177
|
+
|
178
|
+
def closed_producer_check(method)
|
179
|
+
raise Rdkafka::ClosedProducerError.new(method) if @native_kafka.nil?
|
180
|
+
end
|
166
181
|
end
|
167
182
|
end
|
data/lib/rdkafka/version.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module Rdkafka
|
2
|
-
VERSION = "0.
|
3
|
-
LIBRDKAFKA_VERSION = "1.
|
4
|
-
LIBRDKAFKA_SOURCE_SHA256 = "
|
2
|
+
VERSION = "0.11.0"
|
3
|
+
LIBRDKAFKA_VERSION = "1.8.2"
|
4
|
+
LIBRDKAFKA_SOURCE_SHA256 = "6a747d293a7a4613bd2897e28e8791476fbe1ae7361f2530a876e0fd483482a6"
|
5
5
|
end
|
data/lib/rdkafka.rb
CHANGED
@@ -1,6 +1,13 @@
|
|
1
1
|
require "rdkafka/version"
|
2
2
|
|
3
|
+
require "rdkafka/abstract_handle"
|
4
|
+
require "rdkafka/admin"
|
5
|
+
require "rdkafka/admin/create_topic_handle"
|
6
|
+
require "rdkafka/admin/create_topic_report"
|
7
|
+
require "rdkafka/admin/delete_topic_handle"
|
8
|
+
require "rdkafka/admin/delete_topic_report"
|
3
9
|
require "rdkafka/bindings"
|
10
|
+
require "rdkafka/callbacks"
|
4
11
|
require "rdkafka/config"
|
5
12
|
require "rdkafka/consumer"
|
6
13
|
require "rdkafka/consumer/headers"
|
data/rdkafka.gemspec
CHANGED
@@ -14,15 +14,17 @@ Gem::Specification.new do |gem|
|
|
14
14
|
gem.name = 'rdkafka'
|
15
15
|
gem.require_paths = ['lib']
|
16
16
|
gem.version = Rdkafka::VERSION
|
17
|
-
gem.required_ruby_version = '>= 2.
|
17
|
+
gem.required_ruby_version = '>= 2.6'
|
18
18
|
gem.extensions = %w(ext/Rakefile)
|
19
19
|
|
20
|
-
gem.add_dependency 'ffi', '~> 1.
|
21
|
-
gem.add_dependency 'mini_portile2', '~> 2.
|
22
|
-
gem.add_dependency 'rake', '
|
20
|
+
gem.add_dependency 'ffi', '~> 1.15'
|
21
|
+
gem.add_dependency 'mini_portile2', '~> 2.7'
|
22
|
+
gem.add_dependency 'rake', '> 12'
|
23
23
|
|
24
|
-
gem.add_development_dependency 'pry'
|
24
|
+
gem.add_development_dependency 'pry'
|
25
25
|
gem.add_development_dependency 'rspec', '~> 3.5'
|
26
|
-
gem.add_development_dependency 'rake'
|
27
|
-
gem.add_development_dependency 'simplecov'
|
26
|
+
gem.add_development_dependency 'rake'
|
27
|
+
gem.add_development_dependency 'simplecov'
|
28
|
+
gem.add_development_dependency 'guard'
|
29
|
+
gem.add_development_dependency 'guard-rspec'
|
28
30
|
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Rdkafka::AbstractHandle do
|
4
|
+
let(:response) { 0 }
|
5
|
+
let(:result) { -1 }
|
6
|
+
|
7
|
+
context "A subclass that does not implement the required methods" do
|
8
|
+
|
9
|
+
class BadTestHandle < Rdkafka::AbstractHandle
|
10
|
+
layout :pending, :bool,
|
11
|
+
:response, :int
|
12
|
+
end
|
13
|
+
|
14
|
+
it "raises an exception if operation_name is called" do
|
15
|
+
expect {
|
16
|
+
BadTestHandle.new.operation_name
|
17
|
+
}.to raise_exception(RuntimeError, /Must be implemented by subclass!/)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "raises an exception if create_result is called" do
|
21
|
+
expect {
|
22
|
+
BadTestHandle.new.create_result
|
23
|
+
}.to raise_exception(RuntimeError, /Must be implemented by subclass!/)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class TestHandle < Rdkafka::AbstractHandle
|
28
|
+
layout :pending, :bool,
|
29
|
+
:response, :int,
|
30
|
+
:result, :int
|
31
|
+
|
32
|
+
def operation_name
|
33
|
+
"test_operation"
|
34
|
+
end
|
35
|
+
|
36
|
+
def create_result
|
37
|
+
self[:result]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
subject do
|
42
|
+
TestHandle.new.tap do |handle|
|
43
|
+
handle[:pending] = pending_handle
|
44
|
+
handle[:response] = response
|
45
|
+
handle[:result] = result
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe ".register and .remove" do
|
50
|
+
let(:pending_handle) { true }
|
51
|
+
|
52
|
+
it "should register and remove a delivery handle" do
|
53
|
+
Rdkafka::AbstractHandle.register(subject)
|
54
|
+
removed = Rdkafka::AbstractHandle.remove(subject.to_ptr.address)
|
55
|
+
expect(removed).to eq subject
|
56
|
+
expect(Rdkafka::AbstractHandle::REGISTRY).to be_empty
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "#pending?" do
|
61
|
+
context "when true" do
|
62
|
+
let(:pending_handle) { true }
|
63
|
+
|
64
|
+
it "should be true" do
|
65
|
+
expect(subject.pending?).to be true
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context "when not true" do
|
70
|
+
let(:pending_handle) { false }
|
71
|
+
|
72
|
+
it "should be false" do
|
73
|
+
expect(subject.pending?).to be false
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe "#wait" do
|
79
|
+
let(:pending_handle) { true }
|
80
|
+
|
81
|
+
it "should wait until the timeout and then raise an error" do
|
82
|
+
expect {
|
83
|
+
subject.wait(max_wait_timeout: 0.1)
|
84
|
+
}.to raise_error Rdkafka::AbstractHandle::WaitTimeoutError, /test_operation/
|
85
|
+
end
|
86
|
+
|
87
|
+
context "when not pending anymore and no error" do
|
88
|
+
let(:pending_handle) { false }
|
89
|
+
let(:result) { 1 }
|
90
|
+
|
91
|
+
it "should return a result" do
|
92
|
+
wait_result = subject.wait
|
93
|
+
expect(wait_result).to eq(result)
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should wait without a timeout" do
|
97
|
+
wait_result = subject.wait(max_wait_timeout: nil)
|
98
|
+
expect(wait_result).to eq(result)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context "when not pending anymore and there was an error" do
|
103
|
+
let(:pending_handle) { false }
|
104
|
+
let(:response) { 20 }
|
105
|
+
|
106
|
+
it "should raise an rdkafka error" do
|
107
|
+
expect {
|
108
|
+
subject.wait
|
109
|
+
}.to raise_error Rdkafka::RdkafkaError
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Rdkafka::Admin::CreateTopicHandle do
|
4
|
+
let(:response) { 0 }
|
5
|
+
|
6
|
+
subject do
|
7
|
+
Rdkafka::Admin::CreateTopicHandle.new.tap do |handle|
|
8
|
+
handle[:pending] = pending_handle
|
9
|
+
handle[:response] = response
|
10
|
+
handle[:error_string] = FFI::Pointer::NULL
|
11
|
+
handle[:result_name] = FFI::MemoryPointer.from_string("my-test-topic")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "#wait" do
|
16
|
+
let(:pending_handle) { true }
|
17
|
+
|
18
|
+
it "should wait until the timeout and then raise an error" do
|
19
|
+
expect {
|
20
|
+
subject.wait(max_wait_timeout: 0.1)
|
21
|
+
}.to raise_error Rdkafka::Admin::CreateTopicHandle::WaitTimeoutError, /create topic/
|
22
|
+
end
|
23
|
+
|
24
|
+
context "when not pending anymore and no error" do
|
25
|
+
let(:pending_handle) { false }
|
26
|
+
|
27
|
+
it "should return a create topic report" do
|
28
|
+
report = subject.wait
|
29
|
+
|
30
|
+
expect(report.error_string).to eq(nil)
|
31
|
+
expect(report.result_name).to eq("my-test-topic")
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should wait without a timeout" do
|
35
|
+
report = subject.wait(max_wait_timeout: nil)
|
36
|
+
|
37
|
+
expect(report.error_string).to eq(nil)
|
38
|
+
expect(report.result_name).to eq("my-test-topic")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "#raise_error" do
|
44
|
+
let(:pending_handle) { false }
|
45
|
+
|
46
|
+
it "should raise the appropriate error" do
|
47
|
+
expect {
|
48
|
+
subject.raise_error
|
49
|
+
}.to raise_exception(Rdkafka::RdkafkaError, /Success \(no_error\)/)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Rdkafka::Admin::CreateTopicReport do
|
4
|
+
subject { Rdkafka::Admin::CreateTopicReport.new(
|
5
|
+
FFI::MemoryPointer.from_string("error string"),
|
6
|
+
FFI::MemoryPointer.from_string("result name")
|
7
|
+
)}
|
8
|
+
|
9
|
+
it "should get the error string" do
|
10
|
+
expect(subject.error_string).to eq("error string")
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should get the result name" do
|
14
|
+
expect(subject.result_name).to eq("result name")
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Rdkafka::Admin::DeleteTopicHandle do
|
4
|
+
let(:response) { 0 }
|
5
|
+
|
6
|
+
subject do
|
7
|
+
Rdkafka::Admin::DeleteTopicHandle.new.tap do |handle|
|
8
|
+
handle[:pending] = pending_handle
|
9
|
+
handle[:response] = response
|
10
|
+
handle[:error_string] = FFI::Pointer::NULL
|
11
|
+
handle[:result_name] = FFI::MemoryPointer.from_string("my-test-topic")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "#wait" do
|
16
|
+
let(:pending_handle) { true }
|
17
|
+
|
18
|
+
it "should wait until the timeout and then raise an error" do
|
19
|
+
expect {
|
20
|
+
subject.wait(max_wait_timeout: 0.1)
|
21
|
+
}.to raise_error Rdkafka::Admin::DeleteTopicHandle::WaitTimeoutError, /delete topic/
|
22
|
+
end
|
23
|
+
|
24
|
+
context "when not pending anymore and no error" do
|
25
|
+
let(:pending_handle) { false }
|
26
|
+
|
27
|
+
it "should return a delete topic report" do
|
28
|
+
report = subject.wait
|
29
|
+
|
30
|
+
expect(report.error_string).to eq(nil)
|
31
|
+
expect(report.result_name).to eq("my-test-topic")
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should wait without a timeout" do
|
35
|
+
report = subject.wait(max_wait_timeout: nil)
|
36
|
+
|
37
|
+
expect(report.error_string).to eq(nil)
|
38
|
+
expect(report.result_name).to eq("my-test-topic")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "#raise_error" do
|
44
|
+
let(:pending_handle) { false }
|
45
|
+
|
46
|
+
it "should raise the appropriate error" do
|
47
|
+
expect {
|
48
|
+
subject.raise_error
|
49
|
+
}.to raise_exception(Rdkafka::RdkafkaError, /Success \(no_error\)/)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Rdkafka::Admin::DeleteTopicReport do
|
4
|
+
subject { Rdkafka::Admin::DeleteTopicReport.new(
|
5
|
+
FFI::MemoryPointer.from_string("error string"),
|
6
|
+
FFI::MemoryPointer.from_string("result name")
|
7
|
+
)}
|
8
|
+
|
9
|
+
it "should get the error string" do
|
10
|
+
expect(subject.error_string).to eq("error string")
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should get the result name" do
|
14
|
+
expect(subject.result_name).to eq("result name")
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,203 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "ostruct"
|
3
|
+
|
4
|
+
describe Rdkafka::Admin do
|
5
|
+
let(:config) { rdkafka_config }
|
6
|
+
let(:admin) { config.admin }
|
7
|
+
|
8
|
+
after do
|
9
|
+
# Registry should always end up being empty
|
10
|
+
expect(Rdkafka::Admin::CreateTopicHandle::REGISTRY).to be_empty
|
11
|
+
admin.close
|
12
|
+
end
|
13
|
+
|
14
|
+
let(:topic_name) { "test-topic-#{Random.new.rand(0..1_000_000)}" }
|
15
|
+
let(:topic_partition_count) { 3 }
|
16
|
+
let(:topic_replication_factor) { 1 }
|
17
|
+
let(:topic_config) { {"cleanup.policy" => "compact", "min.cleanable.dirty.ratio" => 0.8} }
|
18
|
+
let(:invalid_topic_config) { {"cleeeeenup.policee" => "campact"} }
|
19
|
+
|
20
|
+
describe "#create_topic" do
|
21
|
+
describe "called with invalid input" do
|
22
|
+
describe "with an invalid topic name" do
|
23
|
+
# https://github.com/apache/kafka/blob/trunk/clients/src/main/java/org/apache/kafka/common/internals/Topic.java#L29
|
24
|
+
# public static final String LEGAL_CHARS = "[a-zA-Z0-9._-]";
|
25
|
+
let(:topic_name) { "[!@#]" }
|
26
|
+
|
27
|
+
it "raises an exception" do
|
28
|
+
create_topic_handle = admin.create_topic(topic_name, topic_partition_count, topic_replication_factor)
|
29
|
+
expect {
|
30
|
+
create_topic_handle.wait(max_wait_timeout: 15.0)
|
31
|
+
}.to raise_exception { |ex|
|
32
|
+
expect(ex).to be_a(Rdkafka::RdkafkaError)
|
33
|
+
expect(ex.message).to match(/Broker: Invalid topic \(topic_exception\)/)
|
34
|
+
expect(ex.broker_message).to match(/Topic name.*is illegal, it contains a character other than ASCII alphanumerics/)
|
35
|
+
}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "with the name of a topic that already exists" do
|
40
|
+
let(:topic_name) { "empty_test_topic" } # created in spec_helper.rb
|
41
|
+
|
42
|
+
it "raises an exception" do
|
43
|
+
create_topic_handle = admin.create_topic(topic_name, topic_partition_count, topic_replication_factor)
|
44
|
+
expect {
|
45
|
+
create_topic_handle.wait(max_wait_timeout: 15.0)
|
46
|
+
}.to raise_exception { |ex|
|
47
|
+
expect(ex).to be_a(Rdkafka::RdkafkaError)
|
48
|
+
expect(ex.message).to match(/Broker: Topic already exists \(topic_already_exists\)/)
|
49
|
+
expect(ex.broker_message).to match(/Topic 'empty_test_topic' already exists/)
|
50
|
+
}
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "with an invalid partition count" do
|
55
|
+
let(:topic_partition_count) { -999 }
|
56
|
+
|
57
|
+
it "raises an exception" do
|
58
|
+
expect {
|
59
|
+
admin.create_topic(topic_name, topic_partition_count, topic_replication_factor)
|
60
|
+
}.to raise_error Rdkafka::Config::ConfigError, /num_partitions out of expected range/
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "with an invalid replication factor" do
|
65
|
+
let(:topic_replication_factor) { -2 }
|
66
|
+
|
67
|
+
it "raises an exception" do
|
68
|
+
expect {
|
69
|
+
admin.create_topic(topic_name, topic_partition_count, topic_replication_factor)
|
70
|
+
}.to raise_error Rdkafka::Config::ConfigError, /replication_factor out of expected range/
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe "with an invalid topic configuration" do
|
75
|
+
it "doesn't create the topic" do
|
76
|
+
create_topic_handle = admin.create_topic(topic_name, topic_partition_count, topic_replication_factor, invalid_topic_config)
|
77
|
+
expect {
|
78
|
+
create_topic_handle.wait(max_wait_timeout: 15.0)
|
79
|
+
}.to raise_error Rdkafka::RdkafkaError, /Broker: Configuration is invalid \(invalid_config\)/
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context "edge case" do
|
85
|
+
context "where we are unable to get the background queue" do
|
86
|
+
before do
|
87
|
+
allow(Rdkafka::Bindings).to receive(:rd_kafka_queue_get_background).and_return(FFI::Pointer::NULL)
|
88
|
+
end
|
89
|
+
|
90
|
+
it "raises an exception" do
|
91
|
+
expect {
|
92
|
+
admin.create_topic(topic_name, topic_partition_count, topic_replication_factor)
|
93
|
+
}.to raise_error Rdkafka::Config::ConfigError, /rd_kafka_queue_get_background was NULL/
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
context "where rd_kafka_CreateTopics raises an exception" do
|
98
|
+
before do
|
99
|
+
allow(Rdkafka::Bindings).to receive(:rd_kafka_CreateTopics).and_raise(RuntimeError.new("oops"))
|
100
|
+
end
|
101
|
+
|
102
|
+
it "raises an exception" do
|
103
|
+
expect {
|
104
|
+
admin.create_topic(topic_name, topic_partition_count, topic_replication_factor)
|
105
|
+
}.to raise_error RuntimeError, /oops/
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
it "creates a topic" do
|
111
|
+
create_topic_handle = admin.create_topic(topic_name, topic_partition_count, topic_replication_factor, topic_config)
|
112
|
+
create_topic_report = create_topic_handle.wait(max_wait_timeout: 15.0)
|
113
|
+
expect(create_topic_report.error_string).to be_nil
|
114
|
+
expect(create_topic_report.result_name).to eq(topic_name)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe "#delete_topic" do
|
119
|
+
describe "called with invalid input" do
|
120
|
+
# https://github.com/apache/kafka/blob/trunk/clients/src/main/java/org/apache/kafka/common/internals/Topic.java#L29
|
121
|
+
# public static final String LEGAL_CHARS = "[a-zA-Z0-9._-]";
|
122
|
+
describe "with an invalid topic name" do
|
123
|
+
let(:topic_name) { "[!@#]" }
|
124
|
+
|
125
|
+
it "raises an exception" do
|
126
|
+
delete_topic_handle = admin.delete_topic(topic_name)
|
127
|
+
expect {
|
128
|
+
delete_topic_handle.wait(max_wait_timeout: 15.0)
|
129
|
+
}.to raise_exception { |ex|
|
130
|
+
expect(ex).to be_a(Rdkafka::RdkafkaError)
|
131
|
+
expect(ex.message).to match(/Broker: Unknown topic or partition \(unknown_topic_or_part\)/)
|
132
|
+
expect(ex.broker_message).to match(/Broker: Unknown topic or partition/)
|
133
|
+
}
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
describe "with the name of a topic that does not exist" do
|
138
|
+
it "raises an exception" do
|
139
|
+
delete_topic_handle = admin.delete_topic(topic_name)
|
140
|
+
expect {
|
141
|
+
delete_topic_handle.wait(max_wait_timeout: 15.0)
|
142
|
+
}.to raise_exception { |ex|
|
143
|
+
expect(ex).to be_a(Rdkafka::RdkafkaError)
|
144
|
+
expect(ex.message).to match(/Broker: Unknown topic or partition \(unknown_topic_or_part\)/)
|
145
|
+
expect(ex.broker_message).to match(/Broker: Unknown topic or partition/)
|
146
|
+
}
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
context "edge case" do
|
152
|
+
context "where we are unable to get the background queue" do
|
153
|
+
before do
|
154
|
+
allow(Rdkafka::Bindings).to receive(:rd_kafka_queue_get_background).and_return(FFI::Pointer::NULL)
|
155
|
+
end
|
156
|
+
|
157
|
+
it "raises an exception" do
|
158
|
+
expect {
|
159
|
+
admin.delete_topic(topic_name)
|
160
|
+
}.to raise_error Rdkafka::Config::ConfigError, /rd_kafka_queue_get_background was NULL/
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
context "where rd_kafka_DeleteTopics raises an exception" do
|
165
|
+
before do
|
166
|
+
allow(Rdkafka::Bindings).to receive(:rd_kafka_DeleteTopics).and_raise(RuntimeError.new("oops"))
|
167
|
+
end
|
168
|
+
|
169
|
+
it "raises an exception" do
|
170
|
+
expect {
|
171
|
+
admin.delete_topic(topic_name)
|
172
|
+
}.to raise_error RuntimeError, /oops/
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
|
178
|
+
it "deletes a topic that was newly created" do
|
179
|
+
create_topic_handle = admin.create_topic(topic_name, topic_partition_count, topic_replication_factor)
|
180
|
+
create_topic_report = create_topic_handle.wait(max_wait_timeout: 15.0)
|
181
|
+
expect(create_topic_report.error_string).to be_nil
|
182
|
+
expect(create_topic_report.result_name).to eq(topic_name)
|
183
|
+
|
184
|
+
# Retry topic deletion a few times. On CI Kafka seems to not
|
185
|
+
# always be ready for it immediately
|
186
|
+
delete_topic_report = nil
|
187
|
+
10.times do |i|
|
188
|
+
begin
|
189
|
+
delete_topic_handle = admin.delete_topic(topic_name)
|
190
|
+
delete_topic_report = delete_topic_handle.wait(max_wait_timeout: 15.0)
|
191
|
+
break
|
192
|
+
rescue Rdkafka::RdkafkaError => ex
|
193
|
+
if i > 3
|
194
|
+
raise ex
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
expect(delete_topic_report.error_string).to be_nil
|
200
|
+
expect(delete_topic_report.result_name).to eq(topic_name)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|