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