rdkafka 0.8.0 → 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.semaphore/semaphore.yml +23 -0
  3. data/CHANGELOG.md +6 -0
  4. data/README.md +5 -2
  5. data/docker-compose.yml +2 -0
  6. data/ext/Rakefile +1 -1
  7. data/lib/rdkafka.rb +7 -0
  8. data/lib/rdkafka/abstract_handle.rb +82 -0
  9. data/lib/rdkafka/admin.rb +144 -0
  10. data/lib/rdkafka/admin/create_topic_handle.rb +27 -0
  11. data/lib/rdkafka/admin/create_topic_report.rb +22 -0
  12. data/lib/rdkafka/admin/delete_topic_handle.rb +27 -0
  13. data/lib/rdkafka/admin/delete_topic_report.rb +22 -0
  14. data/lib/rdkafka/bindings.rb +44 -17
  15. data/lib/rdkafka/callbacks.rb +106 -0
  16. data/lib/rdkafka/config.rb +14 -1
  17. data/lib/rdkafka/consumer.rb +35 -5
  18. data/lib/rdkafka/error.rb +29 -3
  19. data/lib/rdkafka/metadata.rb +6 -5
  20. data/lib/rdkafka/producer.rb +13 -2
  21. data/lib/rdkafka/producer/delivery_handle.rb +7 -53
  22. data/lib/rdkafka/version.rb +1 -1
  23. data/spec/rdkafka/abstract_handle_spec.rb +114 -0
  24. data/spec/rdkafka/admin/create_topic_handle_spec.rb +52 -0
  25. data/spec/rdkafka/admin/create_topic_report_spec.rb +16 -0
  26. data/spec/rdkafka/admin/delete_topic_handle_spec.rb +52 -0
  27. data/spec/rdkafka/admin/delete_topic_report_spec.rb +16 -0
  28. data/spec/rdkafka/admin_spec.rb +192 -0
  29. data/spec/rdkafka/callbacks_spec.rb +20 -0
  30. data/spec/rdkafka/config_spec.rb +11 -0
  31. data/spec/rdkafka/consumer_spec.rb +34 -2
  32. data/spec/rdkafka/error_spec.rb +4 -0
  33. data/spec/rdkafka/metadata_spec.rb +78 -0
  34. data/spec/rdkafka/producer/delivery_handle_spec.rb +1 -41
  35. data/spec/rdkafka/producer_spec.rb +22 -0
  36. data/spec/spec_helper.rb +28 -11
  37. metadata +26 -3
  38. data/.travis.yml +0 -48
@@ -1,5 +1,5 @@
1
1
  module Rdkafka
2
- VERSION = "0.8.0"
2
+ VERSION = "0.8.1"
3
3
  LIBRDKAFKA_VERSION = "1.4.0"
4
4
  LIBRDKAFKA_SOURCE_SHA256 = "ae27ea3f3d0d32d29004e7f709efbba2666c5383a107cc45b3a1949486b2eb84"
5
5
  end
@@ -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