rdkafka 0.8.0 → 0.8.1
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/.semaphore/semaphore.yml +23 -0
- data/CHANGELOG.md +6 -0
- data/README.md +5 -2
- data/docker-compose.yml +2 -0
- data/ext/Rakefile +1 -1
- data/lib/rdkafka.rb +7 -0
- data/lib/rdkafka/abstract_handle.rb +82 -0
- data/lib/rdkafka/admin.rb +144 -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/bindings.rb +44 -17
- data/lib/rdkafka/callbacks.rb +106 -0
- data/lib/rdkafka/config.rb +14 -1
- data/lib/rdkafka/consumer.rb +35 -5
- data/lib/rdkafka/error.rb +29 -3
- data/lib/rdkafka/metadata.rb +6 -5
- data/lib/rdkafka/producer.rb +13 -2
- data/lib/rdkafka/producer/delivery_handle.rb +7 -53
- data/lib/rdkafka/version.rb +1 -1
- data/spec/rdkafka/abstract_handle_spec.rb +114 -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 +192 -0
- data/spec/rdkafka/callbacks_spec.rb +20 -0
- data/spec/rdkafka/config_spec.rb +11 -0
- data/spec/rdkafka/consumer_spec.rb +34 -2
- 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 +22 -0
- data/spec/spec_helper.rb +28 -11
- metadata +26 -3
- data/.travis.yml +0 -48
data/lib/rdkafka/version.rb
CHANGED
@@ -0,0 +1,114 @@
|
|
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
|
114
|
+
|
@@ -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,192 @@
|
|
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
|
+
|
18
|
+
describe "#create_topic" do
|
19
|
+
describe "called with invalid input" do
|
20
|
+
describe "with an invalid topic name" do
|
21
|
+
# https://github.com/apache/kafka/blob/trunk/clients/src/main/java/org/apache/kafka/common/internals/Topic.java#L29
|
22
|
+
# public static final String LEGAL_CHARS = "[a-zA-Z0-9._-]";
|
23
|
+
let(:topic_name) { "[!@#]" }
|
24
|
+
|
25
|
+
it "raises an exception" do
|
26
|
+
create_topic_handle = admin.create_topic(topic_name, topic_partition_count, topic_replication_factor)
|
27
|
+
expect {
|
28
|
+
create_topic_handle.wait(max_wait_timeout: 15.0)
|
29
|
+
}.to raise_exception { |ex|
|
30
|
+
expect(ex).to be_a(Rdkafka::RdkafkaError)
|
31
|
+
expect(ex.message).to match(/Broker: Invalid topic \(topic_exception\)/)
|
32
|
+
expect(ex.broker_message).to match(/Topic name.*is illegal, it contains a character other than ASCII alphanumerics/)
|
33
|
+
}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "with the name of a topic that already exists" do
|
38
|
+
let(:topic_name) { "empty_test_topic" } # created in spec_helper.rb
|
39
|
+
|
40
|
+
it "raises an exception" do
|
41
|
+
create_topic_handle = admin.create_topic(topic_name, topic_partition_count, topic_replication_factor)
|
42
|
+
expect {
|
43
|
+
create_topic_handle.wait(max_wait_timeout: 15.0)
|
44
|
+
}.to raise_exception { |ex|
|
45
|
+
expect(ex).to be_a(Rdkafka::RdkafkaError)
|
46
|
+
expect(ex.message).to match(/Broker: Topic already exists \(topic_already_exists\)/)
|
47
|
+
expect(ex.broker_message).to match(/Topic 'empty_test_topic' already exists/)
|
48
|
+
}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "with an invalid partition count" do
|
53
|
+
let(:topic_partition_count) { -1 }
|
54
|
+
|
55
|
+
it "raises an exception" do
|
56
|
+
expect {
|
57
|
+
admin.create_topic(topic_name, topic_partition_count, topic_replication_factor)
|
58
|
+
}.to raise_error Rdkafka::Config::ConfigError, /num_partitions out of expected range/
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "with an invalid replication factor" do
|
63
|
+
let(:topic_replication_factor) { -2 }
|
64
|
+
|
65
|
+
it "raises an exception" do
|
66
|
+
expect {
|
67
|
+
admin.create_topic(topic_name, topic_partition_count, topic_replication_factor)
|
68
|
+
}.to raise_error Rdkafka::Config::ConfigError, /replication_factor out of expected range/
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context "edge case" do
|
74
|
+
context "where we are unable to get the background queue" do
|
75
|
+
before do
|
76
|
+
allow(Rdkafka::Bindings).to receive(:rd_kafka_queue_get_background).and_return(FFI::Pointer::NULL)
|
77
|
+
end
|
78
|
+
|
79
|
+
it "raises an exception" do
|
80
|
+
expect {
|
81
|
+
admin.create_topic(topic_name, topic_partition_count, topic_replication_factor)
|
82
|
+
}.to raise_error Rdkafka::Config::ConfigError, /rd_kafka_queue_get_background was NULL/
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
context "where rd_kafka_CreateTopics raises an exception" do
|
87
|
+
before do
|
88
|
+
allow(Rdkafka::Bindings).to receive(:rd_kafka_CreateTopics).and_raise(RuntimeError.new("oops"))
|
89
|
+
end
|
90
|
+
|
91
|
+
it "raises an exception" do
|
92
|
+
expect {
|
93
|
+
admin.create_topic(topic_name, topic_partition_count, topic_replication_factor)
|
94
|
+
}.to raise_error RuntimeError, /oops/
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
it "creates a topic" do
|
100
|
+
create_topic_handle = admin.create_topic(topic_name, topic_partition_count, topic_replication_factor)
|
101
|
+
create_topic_report = create_topic_handle.wait(max_wait_timeout: 15.0)
|
102
|
+
expect(create_topic_report.error_string).to be_nil
|
103
|
+
expect(create_topic_report.result_name).to eq(topic_name)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "#delete_topic" do
|
108
|
+
describe "called with invalid input" do
|
109
|
+
# https://github.com/apache/kafka/blob/trunk/clients/src/main/java/org/apache/kafka/common/internals/Topic.java#L29
|
110
|
+
# public static final String LEGAL_CHARS = "[a-zA-Z0-9._-]";
|
111
|
+
describe "with an invalid topic name" do
|
112
|
+
let(:topic_name) { "[!@#]" }
|
113
|
+
|
114
|
+
it "raises an exception" do
|
115
|
+
delete_topic_handle = admin.delete_topic(topic_name)
|
116
|
+
expect {
|
117
|
+
delete_topic_handle.wait(max_wait_timeout: 15.0)
|
118
|
+
}.to raise_exception { |ex|
|
119
|
+
expect(ex).to be_a(Rdkafka::RdkafkaError)
|
120
|
+
expect(ex.message).to match(/Broker: Unknown topic or partition \(unknown_topic_or_part\)/)
|
121
|
+
expect(ex.broker_message).to match(/Broker: Unknown topic or partition/)
|
122
|
+
}
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe "with the name of a topic that does not exist" do
|
127
|
+
it "raises an exception" do
|
128
|
+
delete_topic_handle = admin.delete_topic(topic_name)
|
129
|
+
expect {
|
130
|
+
delete_topic_handle.wait(max_wait_timeout: 15.0)
|
131
|
+
}.to raise_exception { |ex|
|
132
|
+
expect(ex).to be_a(Rdkafka::RdkafkaError)
|
133
|
+
expect(ex.message).to match(/Broker: Unknown topic or partition \(unknown_topic_or_part\)/)
|
134
|
+
expect(ex.broker_message).to match(/Broker: Unknown topic or partition/)
|
135
|
+
}
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
context "edge case" do
|
141
|
+
context "where we are unable to get the background queue" do
|
142
|
+
before do
|
143
|
+
allow(Rdkafka::Bindings).to receive(:rd_kafka_queue_get_background).and_return(FFI::Pointer::NULL)
|
144
|
+
end
|
145
|
+
|
146
|
+
it "raises an exception" do
|
147
|
+
expect {
|
148
|
+
admin.delete_topic(topic_name)
|
149
|
+
}.to raise_error Rdkafka::Config::ConfigError, /rd_kafka_queue_get_background was NULL/
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
context "where rd_kafka_DeleteTopics raises an exception" do
|
154
|
+
before do
|
155
|
+
allow(Rdkafka::Bindings).to receive(:rd_kafka_DeleteTopics).and_raise(RuntimeError.new("oops"))
|
156
|
+
end
|
157
|
+
|
158
|
+
it "raises an exception" do
|
159
|
+
expect {
|
160
|
+
admin.delete_topic(topic_name)
|
161
|
+
}.to raise_error RuntimeError, /oops/
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
|
167
|
+
it "deletes a topic that was newly created" do
|
168
|
+
create_topic_handle = admin.create_topic(topic_name, topic_partition_count, topic_replication_factor)
|
169
|
+
create_topic_report = create_topic_handle.wait(max_wait_timeout: 15.0)
|
170
|
+
expect(create_topic_report.error_string).to be_nil
|
171
|
+
expect(create_topic_report.result_name).to eq(topic_name)
|
172
|
+
|
173
|
+
# Retry topic deletion a few times. On CI Kafka seems to not
|
174
|
+
# always be ready for it immediately
|
175
|
+
delete_topic_report = nil
|
176
|
+
10.times do |i|
|
177
|
+
begin
|
178
|
+
delete_topic_handle = admin.delete_topic(topic_name)
|
179
|
+
delete_topic_report = delete_topic_handle.wait(max_wait_timeout: 15.0)
|
180
|
+
break
|
181
|
+
rescue Rdkafka::RdkafkaError => ex
|
182
|
+
if i > 3
|
183
|
+
raise ex
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
expect(delete_topic_report.error_string).to be_nil
|
189
|
+
expect(delete_topic_report.result_name).to eq(topic_name)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|