poseidon 0.0.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.
- data/.gitignore +19 -0
- data/.rspec +2 -0
- data/.travis.yml +12 -0
- data/.yardopts +8 -0
- data/Gemfile +13 -0
- data/LICENSE.txt +22 -0
- data/README.md +71 -0
- data/Rakefile +17 -0
- data/TODO.md +27 -0
- data/examples/consumer.rb +18 -0
- data/examples/producer.rb +9 -0
- data/lib/poseidon/broker_pool.rb +72 -0
- data/lib/poseidon/cluster_metadata.rb +63 -0
- data/lib/poseidon/compressed_value.rb +23 -0
- data/lib/poseidon/compression/gzip_codec.rb +23 -0
- data/lib/poseidon/compression/snappy_codec.rb +17 -0
- data/lib/poseidon/compression.rb +30 -0
- data/lib/poseidon/connection.rb +138 -0
- data/lib/poseidon/fetched_message.rb +37 -0
- data/lib/poseidon/message.rb +151 -0
- data/lib/poseidon/message_conductor.rb +84 -0
- data/lib/poseidon/message_set.rb +80 -0
- data/lib/poseidon/message_to_send.rb +33 -0
- data/lib/poseidon/messages_for_broker.rb +39 -0
- data/lib/poseidon/messages_to_send.rb +47 -0
- data/lib/poseidon/messages_to_send_batch.rb +27 -0
- data/lib/poseidon/partition_consumer.rb +154 -0
- data/lib/poseidon/producer.rb +193 -0
- data/lib/poseidon/producer_compression_config.rb +36 -0
- data/lib/poseidon/protocol/protocol_struct.rb +238 -0
- data/lib/poseidon/protocol/request_buffer.rb +78 -0
- data/lib/poseidon/protocol/response_buffer.rb +72 -0
- data/lib/poseidon/protocol.rb +122 -0
- data/lib/poseidon/sync_producer.rb +117 -0
- data/lib/poseidon/topic_metadata.rb +65 -0
- data/lib/poseidon/version.rb +4 -0
- data/lib/poseidon.rb +102 -0
- data/poseidon.gemspec +24 -0
- data/spec/bin/kafka-run-class.sh +65 -0
- data/spec/integration/multiple_brokers/round_robin_spec.rb +39 -0
- data/spec/integration/multiple_brokers/spec_helper.rb +34 -0
- data/spec/integration/simple/compression_spec.rb +20 -0
- data/spec/integration/simple/connection_spec.rb +33 -0
- data/spec/integration/simple/multiple_brokers_spec.rb +8 -0
- data/spec/integration/simple/simple_producer_and_consumer_spec.rb +97 -0
- data/spec/integration/simple/spec_helper.rb +17 -0
- data/spec/integration/simple/unavailable_broker_spec.rb +77 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/test_cluster.rb +205 -0
- data/spec/unit/broker_pool_spec.rb +77 -0
- data/spec/unit/cluster_metadata_spec.rb +41 -0
- data/spec/unit/compression_spec.rb +17 -0
- data/spec/unit/connection_spec.rb +4 -0
- data/spec/unit/fetched_message_spec.rb +11 -0
- data/spec/unit/message_conductor_spec.rb +147 -0
- data/spec/unit/message_set_spec.rb +42 -0
- data/spec/unit/message_spec.rb +112 -0
- data/spec/unit/message_to_send_spec.rb +10 -0
- data/spec/unit/messages_for_broker_spec.rb +54 -0
- data/spec/unit/messages_to_send_batch_spec.rb +25 -0
- data/spec/unit/messages_to_send_spec.rb +63 -0
- data/spec/unit/partition_consumer_spec.rb +124 -0
- data/spec/unit/producer_compression_config_spec.rb +35 -0
- data/spec/unit/producer_spec.rb +45 -0
- data/spec/unit/protocol_spec.rb +54 -0
- data/spec/unit/sync_producer_spec.rb +141 -0
- data/spec/unit/topic_metadata_spec.rb +17 -0
- metadata +206 -0
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
include Protocol
|
4
|
+
describe MessageConductor do
|
5
|
+
context "two avialable partitions" do
|
6
|
+
before(:each) do
|
7
|
+
partitions = [
|
8
|
+
PartitionMetadata.new(nil, 0, 1, [1,2], [1,2]),
|
9
|
+
PartitionMetadata.new(nil, 1, 2, [2,1], [2,1])
|
10
|
+
]
|
11
|
+
topics = [TopicMetadata.new(TopicMetadataStruct.new(nil, "test", partitions))]
|
12
|
+
brokers = [Broker.new(1, "host1", 1), Broker.new(2, "host2", 2)]
|
13
|
+
|
14
|
+
@mr = MetadataResponse.new(nil, brokers, topics)
|
15
|
+
|
16
|
+
@cm = ClusterMetadata.new
|
17
|
+
@cm.update(@mr)
|
18
|
+
end
|
19
|
+
|
20
|
+
context "no custom partitioner" do
|
21
|
+
before(:each) do
|
22
|
+
@mc = MessageConductor.new(@cm, nil)
|
23
|
+
end
|
24
|
+
|
25
|
+
context "for unkeyed messages" do
|
26
|
+
it "round robins which partition the message should go to" do
|
27
|
+
[0,1,0,1].each do |destination|
|
28
|
+
expect(@mc.destination("test").first).to eq(destination)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context "unknown topic" do
|
33
|
+
it "returns -1 for broker and partition" do
|
34
|
+
expect(@mc.destination("no_exist")).to eq([-1,-1])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context "keyed message" do
|
40
|
+
it "sends the same keys to the same destinations" do
|
41
|
+
keys = 1000.times.map { rand(500).to_s }
|
42
|
+
key_destinations = {}
|
43
|
+
|
44
|
+
keys.sort_by { rand }.each do |k|
|
45
|
+
partition,broker = @mc.destination("test", k)
|
46
|
+
|
47
|
+
key_destinations[k] ||= []
|
48
|
+
key_destinations[k].push([partition,broker])
|
49
|
+
end
|
50
|
+
|
51
|
+
expect(key_destinations.values.all? { |destinations| destinations.uniq.size == 1 }).to eq(true)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context "custom partitioner" do
|
57
|
+
before(:each) do
|
58
|
+
partitioner = Proc.new { |key, count| key.split("_").first.to_i % count }
|
59
|
+
@mc = MessageConductor.new(@cm, partitioner)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "obeys custom partitioner" do
|
63
|
+
expect(@mc.destination("test", "2_hello").first).to eq(0)
|
64
|
+
expect(@mc.destination("test", "3_hello").first).to eq(1)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context "broken partitioner" do
|
69
|
+
before(:each) do
|
70
|
+
partitioner = Proc.new { |key, count| count + 1 }
|
71
|
+
@mc = MessageConductor.new(@cm, partitioner)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "raises InvalidPartitionError" do
|
75
|
+
expect{@mc.destination("test", "2_hello").first}.to raise_error(Errors::InvalidPartitionError)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context "two partitions, one is unavailable" do
|
81
|
+
before(:each) do
|
82
|
+
partitions = [
|
83
|
+
Protocol::PartitionMetadata.new(nil, 0, 1, [1,2], [1,2]),
|
84
|
+
Protocol::PartitionMetadata.new(nil, 1, nil, [2,1], [2,1])
|
85
|
+
]
|
86
|
+
topics = [TopicMetadata.new(TopicMetadataStruct.new(nil, "test", partitions))]
|
87
|
+
brokers = [Broker.new(1, "host1", 1), Broker.new(2, "host2", 2)]
|
88
|
+
|
89
|
+
@mr = MetadataResponse.new(nil, brokers, topics)
|
90
|
+
|
91
|
+
@cm = ClusterMetadata.new
|
92
|
+
@cm.update(@mr)
|
93
|
+
|
94
|
+
@mc = MessageConductor.new(@cm, nil)
|
95
|
+
end
|
96
|
+
|
97
|
+
context "keyless message" do
|
98
|
+
it "is never sent to an unavailable partition" do
|
99
|
+
10.times do |destination|
|
100
|
+
expect(@mc.destination("test").first).to eq(0)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
context "keyed message" do
|
106
|
+
it "is sent to unavailable partition" do
|
107
|
+
destinations = Set.new
|
108
|
+
100.times do |key|
|
109
|
+
destinations << @mc.destination("test",key.to_s).first
|
110
|
+
end
|
111
|
+
expect(destinations).to eq(Set.new([0,1]))
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context "no available partitions" do
|
117
|
+
before(:each) do
|
118
|
+
partitions = [
|
119
|
+
Protocol::PartitionMetadata.new(nil, 0, nil, [1,2], [1,2]),
|
120
|
+
Protocol::PartitionMetadata.new(nil, 1, nil, [2,1], [2,1])
|
121
|
+
]
|
122
|
+
topics = [TopicMetadata.new(TopicMetadataStruct.new(nil, "test", partitions))]
|
123
|
+
brokers = [Broker.new(1, "host1", 1), Broker.new(2, "host2", 2)]
|
124
|
+
|
125
|
+
@mr = MetadataResponse.new(nil, brokers, topics)
|
126
|
+
|
127
|
+
@cm = ClusterMetadata.new
|
128
|
+
@cm.update(@mr)
|
129
|
+
|
130
|
+
@mc = MessageConductor.new(@cm, nil)
|
131
|
+
end
|
132
|
+
|
133
|
+
context "keyless message" do
|
134
|
+
it "return -1 for broker and partition" do
|
135
|
+
expect(@mc.destination("test").first).to eq(-1)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
context "keyed message" do
|
140
|
+
it "returns a valid partition and -1 for broker" do
|
141
|
+
partition_id, broker_id = @mc.destination("test", "key")
|
142
|
+
expect(partition_id).to_not eq(-1)
|
143
|
+
expect(broker_id).to eq(-1)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MessageSet do
|
4
|
+
describe "converting to a compressed message" do
|
5
|
+
before(:each) do
|
6
|
+
ms = MessageSet.new([Message.new(:value => "I will be compressed", :topic => "test")])
|
7
|
+
|
8
|
+
@compressed_message_set = ms.compress(Compression::GzipCodec)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "contains a compressed message" do
|
12
|
+
expect(@compressed_message_set.messages.first.compressed?).to eq(true)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "can be decompressed and reconstituted" do
|
16
|
+
expect(@compressed_message_set.flatten.first.value).to eq("I will be compressed")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "adding messages" do
|
21
|
+
it "adds the message to the struct" do
|
22
|
+
m = Message.new(:value => "sup", :topic => "topic")
|
23
|
+
ms = MessageSet.new
|
24
|
+
ms << m
|
25
|
+
expect(ms.struct.messages.first).to eq(m)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "encoding" do
|
30
|
+
it "round trips" do
|
31
|
+
m = Message.new(:value => "sup", :key => "keyz", :topic => "hello")
|
32
|
+
ms = MessageSet.new
|
33
|
+
ms << m
|
34
|
+
|
35
|
+
request_buffer = Protocol::RequestBuffer.new
|
36
|
+
ms.write(request_buffer)
|
37
|
+
|
38
|
+
response_buffer = Protocol::ResponseBuffer.new(request_buffer.to_s)
|
39
|
+
expect(MessageSet.read(response_buffer)).to eq(ms)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Message do
|
4
|
+
describe "when constructing a new message" do
|
5
|
+
it 'raises an ArgumentError on unknown options' do
|
6
|
+
expect { Message.new(:cow => "dog") }.to raise_error(ArgumentError)
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'handles options correctly' do
|
10
|
+
m = Message.new(:value => "value",
|
11
|
+
:key => "key",
|
12
|
+
:attributes => 1,
|
13
|
+
:topic => "topic")
|
14
|
+
|
15
|
+
expect(m.value).to eq("value")
|
16
|
+
expect(m.key).to eq("key")
|
17
|
+
expect(m.compressed?).to eq(true)
|
18
|
+
expect(m.topic).to eq("topic")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "checksum" do
|
23
|
+
context "is incorrect" do
|
24
|
+
before(:each) do
|
25
|
+
m = Message.new(:value => "value",
|
26
|
+
:key => "key",
|
27
|
+
:topic => "topic")
|
28
|
+
|
29
|
+
req_buf = Protocol::RequestBuffer.new
|
30
|
+
m.write(req_buf)
|
31
|
+
|
32
|
+
@s = req_buf.to_s
|
33
|
+
@s[-1] = "q" # break checksum
|
34
|
+
end
|
35
|
+
|
36
|
+
it "knows it" do
|
37
|
+
expect { Message.read(Protocol::ResponseBuffer.new(@s)) }.to raise_error(Errors::ChecksumError)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'is correct' do
|
42
|
+
before(:each) do
|
43
|
+
m = Message.new(:value => "value",
|
44
|
+
:key => "key",
|
45
|
+
:topic => "topic")
|
46
|
+
|
47
|
+
req_buf = Protocol::RequestBuffer.new
|
48
|
+
m.write(req_buf)
|
49
|
+
|
50
|
+
@s = req_buf.to_s
|
51
|
+
end
|
52
|
+
|
53
|
+
it "raises no error" do
|
54
|
+
expect { Message.read(Protocol::ResponseBuffer.new(@s)) }.to_not raise_error
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "truncated message" do
|
60
|
+
before(:each) do
|
61
|
+
m = Message.new(:value => "value",
|
62
|
+
:key => "key",
|
63
|
+
:topic => "topic")
|
64
|
+
|
65
|
+
req_buf = Protocol::RequestBuffer.new
|
66
|
+
m.write(req_buf)
|
67
|
+
|
68
|
+
@s = req_buf.to_s
|
69
|
+
end
|
70
|
+
|
71
|
+
it "reading returns nil" do
|
72
|
+
expect(Message.read(Protocol::ResponseBuffer.new(@s[0..-4]))).to eq(nil)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context "invalid utf8 string for value" do
|
77
|
+
it "builds the payload without error" do
|
78
|
+
s = "asdf\xffasdf"
|
79
|
+
m = Message.new(:value => s,
|
80
|
+
:key => "key",
|
81
|
+
:topic => "topic")
|
82
|
+
|
83
|
+
req_buf = Protocol::RequestBuffer.new
|
84
|
+
expect {
|
85
|
+
m.write(req_buf)
|
86
|
+
}.to_not raise_error
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
context "frozen string for value" do
|
91
|
+
it "builds the payload without error" do
|
92
|
+
s = "asdffasdf".freeze
|
93
|
+
m = Message.new(:value => s,
|
94
|
+
:key => "key",
|
95
|
+
:topic => "topic")
|
96
|
+
|
97
|
+
req_buf = Protocol::RequestBuffer.new
|
98
|
+
expect {
|
99
|
+
m.write(req_buf)
|
100
|
+
}.to_not raise_error
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
it "decompresses a compressed value"
|
105
|
+
|
106
|
+
it "raises an error if you try to decompress an uncompressed value"
|
107
|
+
|
108
|
+
describe "#write" do
|
109
|
+
it 'writes a MessageWithOffsetStruct to the request buffer' do
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MessageToSend do
|
4
|
+
it "provides access to topic,value,key" do
|
5
|
+
mts = MessageToSend.new("hello_topic", "Hello World", "key")
|
6
|
+
expect(mts.topic).to eq("hello_topic")
|
7
|
+
expect(mts.value).to eq("Hello World")
|
8
|
+
expect(mts.key).to eq("key")
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MessagesForBroker do
|
4
|
+
context "twos message one to broker 0, partition 0, another to partition 1" do
|
5
|
+
before(:each) do
|
6
|
+
@messages = [ Message.new(:topic => "topic1",:value => "hi0"),
|
7
|
+
Message.new(:topic => "topic1",:value => "hi1")]
|
8
|
+
|
9
|
+
@compression_config = stub('compression_config',
|
10
|
+
:compression_codec_for_topic => nil)
|
11
|
+
|
12
|
+
@mfb = MessagesForBroker.new(0)
|
13
|
+
@mfb.add(@messages[0], 0)
|
14
|
+
@mfb.add(@messages[1], 1)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "provides the messages" do
|
18
|
+
expect(@mfb.messages.to_set).to eq(@messages.to_set)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "is has a broker_id of 0" do
|
22
|
+
expect(@mfb.broker_id).to eq(0)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "builds the protocol object correctly" do
|
26
|
+
protocol_object = @mfb.build_protocol_objects(@compression_config)
|
27
|
+
|
28
|
+
messages_for_topics = [
|
29
|
+
MessagesForTopic.new("topic1",
|
30
|
+
[
|
31
|
+
MessagesForPartition.new(0, MessageSet.new([@messages[0]])),
|
32
|
+
MessagesForPartition.new(1, MessageSet.new([@messages[1]])),
|
33
|
+
])
|
34
|
+
]
|
35
|
+
expect(protocol_object).to eq(messages_for_topics)
|
36
|
+
end
|
37
|
+
|
38
|
+
context "and topic is compressed" do
|
39
|
+
it "builds the protocol object correctly" do
|
40
|
+
@compression_config.stub!(:compression_codec_for_topic => Compression::GzipCodec)
|
41
|
+
protocol_object = @mfb.build_protocol_objects(@compression_config)
|
42
|
+
|
43
|
+
messages_for_topics = [
|
44
|
+
MessagesForTopic.new("topic1",
|
45
|
+
[
|
46
|
+
MessagesForPartition.new(0, MessageSet.new([@messages[0]]).compress(Compression::GzipCodec)),
|
47
|
+
MessagesForPartition.new(1, MessageSet.new([@messages[1]]).compress(Compression::GzipCodec)),
|
48
|
+
])
|
49
|
+
]
|
50
|
+
expect(protocol_object).to eq(messages_for_topics)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MessagesToSendBatch do
|
4
|
+
context "messages sent to two different brokers" do
|
5
|
+
before(:each) do
|
6
|
+
message_conductor = stub('message_conductor')
|
7
|
+
message_conductor.stub!(:destination).and_return([0,0],[1,1])
|
8
|
+
|
9
|
+
@messages = [
|
10
|
+
Message.new(:topic => "topic1", :value => "hi"),
|
11
|
+
Message.new(:topic => "topic1", :value => "hi")
|
12
|
+
]
|
13
|
+
@batch = MessagesToSendBatch.new(@messages, message_conductor)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "returns a couple messages brokers" do
|
17
|
+
expect(@batch.messages_for_brokers.size).to eq(2)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "has all messages in the returned message brokers" do
|
21
|
+
messages = @batch.messages_for_brokers.map(&:messages).flatten
|
22
|
+
expect(messages.to_set).to eq(@messages.to_set)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MessagesToSend do
|
4
|
+
before(:each) do
|
5
|
+
@messages = []
|
6
|
+
@messages << Message.new(:topic => "test1", :value => "hi")
|
7
|
+
@messages << Message.new(:topic => "test2", :value => "hi")
|
8
|
+
@messages << Message.new(:topic => "test2", :value => "hi")
|
9
|
+
|
10
|
+
|
11
|
+
@cluster_metadata = stub('cluster_metdata').as_null_object
|
12
|
+
@mts = MessagesToSend.new(@messages, @cluster_metadata)
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "needing metadata" do
|
16
|
+
it "returns set of topics" do
|
17
|
+
expect(@mts.topic_set).to eq(Set.new(["test1","test2"]))
|
18
|
+
end
|
19
|
+
|
20
|
+
it "asks ClusterMetadata about having metadata" do
|
21
|
+
@cluster_metadata.stub!(:have_metadata_for_topics?).and_return(true)
|
22
|
+
|
23
|
+
expect(@mts.needs_metadata?).to eq(false)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "sending" do
|
28
|
+
before(:each) do
|
29
|
+
@mfb = stub('mfb', :messages => @messages)
|
30
|
+
@messages_for_brokers = [@mfb]
|
31
|
+
|
32
|
+
@mtsb = stub('messages_to_send_batch').as_null_object
|
33
|
+
@mtsb.stub!(:messages_for_brokers).and_return(@messages_for_brokers)
|
34
|
+
|
35
|
+
MessagesToSendBatch.stub!(:new).and_return(@mtsb)
|
36
|
+
end
|
37
|
+
|
38
|
+
context "is successful" do
|
39
|
+
before(:each) do
|
40
|
+
@mts.messages_for_brokers(nil).each do |mfb|
|
41
|
+
@mts.successfully_sent(mfb)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
it "successfully sends all" do
|
46
|
+
expect(@mts.all_sent?).to eq(true)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "is not successful" do
|
51
|
+
before(:each) do
|
52
|
+
@mts.messages_for_brokers(nil).each do |mfb|
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
it "does not send all" do
|
57
|
+
@mts.messages_for_brokers(nil).each do |mfb|
|
58
|
+
end
|
59
|
+
expect(@mts.all_sent?).to eq(false)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe PartitionConsumer do
|
4
|
+
before(:each) do
|
5
|
+
@connection = stub('connection')
|
6
|
+
Connection.stub!(:new).and_return(@connection)
|
7
|
+
|
8
|
+
offset = Protocol::Offset.new(100)
|
9
|
+
partition_offsets = [Protocol::PartitionOffset.new(0, 0, [offset])]
|
10
|
+
@offset_response = [Protocol::TopicOffsetResponse.new("test_topic", partition_offsets)]
|
11
|
+
@connection.stub(:offset).and_return(@offset_response)
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "creation" do
|
15
|
+
context "when passed unknown options" do
|
16
|
+
it "raises an ArgumentError" do
|
17
|
+
expect { PartitionConsumer.new("test_client", "localhost", 9092, "test_topic", 0,-2, :unknown => true) }.to raise_error(ArgumentError)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context "when passed an unknown offset" do
|
22
|
+
it "raises an ArgumentError" do
|
23
|
+
expect { PartitionConsumer.new("test_client", "localhost", 9092, "test_topic", 0,:coolest_offset) }.to raise_error(ArgumentError)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "next offset" do
|
29
|
+
context "when offset is not set" do
|
30
|
+
it "resolves offset if it's not set" do
|
31
|
+
@connection.should_receive(:offset).and_return(@offset_response)
|
32
|
+
pc = PartitionConsumer.new("test_client", "localhost", 9092, "test_topic",
|
33
|
+
0, -2)
|
34
|
+
|
35
|
+
pc.next_offset
|
36
|
+
end
|
37
|
+
|
38
|
+
it "returns resolved offset" do
|
39
|
+
pc = PartitionConsumer.new("test_client", "localhost", 9092, "test_topic",
|
40
|
+
0, -2)
|
41
|
+
expect(pc.next_offset).to eq(100)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context "when offset is set" do
|
46
|
+
it "does not resolve it" do
|
47
|
+
pc = PartitionConsumer.new("test_client", "localhost", 9092, "test_topic",
|
48
|
+
0, 200)
|
49
|
+
pc.next_offset
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context "when call returns an error" do
|
54
|
+
it "is raised" do
|
55
|
+
@offset_response.first.partition_offsets.first.stub!(:error).and_return(2)
|
56
|
+
pc = PartitionConsumer.new("test_client", "localhost", 9092, "test_topic",
|
57
|
+
0, -2)
|
58
|
+
|
59
|
+
expect { pc.next_offset }.to raise_error(Errors::InvalidMessage)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context "when no offset exists" do
|
64
|
+
it "sets offset to 0" do
|
65
|
+
pc = PartitionConsumer.new("test_client", "localhost", 9092, "test_topic",
|
66
|
+
0, -2)
|
67
|
+
|
68
|
+
@offset_response.first.partition_offsets.first.stub!(:offsets).and_return([])
|
69
|
+
expect(pc.next_offset).to eq(0)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe "fetching messages" do
|
75
|
+
before(:each) do
|
76
|
+
message_set = MessageSet.new
|
77
|
+
message_set << Message.new(:value => "value", :key => "key", :offset => 90)
|
78
|
+
partition_fetch_response = Protocol::PartitionFetchResponse.new(0, 0, 100, message_set)
|
79
|
+
topic_fetch_response = Protocol::TopicFetchResponse.new('test_topic',
|
80
|
+
[partition_fetch_response])
|
81
|
+
@response = Protocol::FetchResponse.new(stub('common'), [topic_fetch_response])
|
82
|
+
|
83
|
+
@connection.stub(:fetch).and_return(@response)
|
84
|
+
@pc = PartitionConsumer.new("test_client", "localhost", 9092, "test_topic", 0, -2)
|
85
|
+
end
|
86
|
+
|
87
|
+
it "returns FetchedMessage objects" do
|
88
|
+
expect(@pc.fetch.first.class).to eq(FetchedMessage)
|
89
|
+
end
|
90
|
+
|
91
|
+
it "uses object defaults" do
|
92
|
+
@connection.should_receive(:fetch).with(10_000, 0, anything)
|
93
|
+
@pc.fetch
|
94
|
+
end
|
95
|
+
|
96
|
+
context "when options are passed" do
|
97
|
+
it "overrides object defaults" do
|
98
|
+
@connection.should_receive(:fetch).with(20_000, 0, anything)
|
99
|
+
@pc = PartitionConsumer.new("test_client", "localhost", 9092, "test_topic", 0, -2, :max_wait_ms => 20_000)
|
100
|
+
|
101
|
+
@pc.fetch
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
context "when call returns an error" do
|
106
|
+
it "is raised" do
|
107
|
+
pfr = @response.topic_fetch_responses.first.partition_fetch_responses.first
|
108
|
+
pfr.stub!(:error).and_return(2)
|
109
|
+
|
110
|
+
expect { @pc.fetch }.to raise_error(Errors::InvalidMessage)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
it "sets the highwater mark" do
|
115
|
+
@pc.fetch
|
116
|
+
expect(@pc.highwater_mark).to eq(100)
|
117
|
+
end
|
118
|
+
|
119
|
+
it "sets the latest offset" do
|
120
|
+
@pc.fetch
|
121
|
+
expect(@pc.next_offset).to eq(91)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ProducerCompressionConfig do
|
4
|
+
describe "creation" do
|
5
|
+
it "raises ArgumentError when codec is unknown" do
|
6
|
+
expect { ProducerCompressionConfig.new(:ripple, nil) }.to raise_error(ArgumentError)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
context "no codec set" do
|
11
|
+
it "compresses no topics" do
|
12
|
+
pcc = ProducerCompressionConfig.new(nil,nil)
|
13
|
+
expect(pcc.compression_codec_for_topic("test")).to eq(false)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "compression codec no topics specified" do
|
18
|
+
it "compresses any topic" do
|
19
|
+
pcc = ProducerCompressionConfig.new(:gzip,nil)
|
20
|
+
expect(pcc.compression_codec_for_topic("test")).to eq(Compression::GzipCodec)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "compression codec set, but only compress 'compressed' topic" do
|
25
|
+
it "compresses 'compressed' topic" do
|
26
|
+
pcc = ProducerCompressionConfig.new(:gzip, ["compressed"])
|
27
|
+
expect(pcc.compression_codec_for_topic("compressed")).to eq(Compression::GzipCodec)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "does not compresses 'test' topic" do
|
31
|
+
pcc = ProducerCompressionConfig.new(:gzip, ["compressed"])
|
32
|
+
expect(pcc.compression_codec_for_topic("test")).to eq(false)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Producer do
|
4
|
+
it "requires brokers and client_id" do
|
5
|
+
expect { Producer.new }.to raise_error
|
6
|
+
end
|
7
|
+
|
8
|
+
it "raises ArgumentError on unknown arguments" do
|
9
|
+
expect { Producer.new([],"client_id", :unknown => true) }.to raise_error(ArgumentError)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "raises ArgumentError unless brokers is an enumerable" do
|
13
|
+
expect { Producer.new("host:port","client_id") }.to raise_error(ArgumentError)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "raises ProducerShutdown if we try to send to a shutdown producer" do
|
17
|
+
p = Producer.new(["host:port"],"client_id")
|
18
|
+
p.shutdown
|
19
|
+
expect { p.send_messages([]) }.to raise_error(Errors::ProducerShutdownError)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "accepts all options" do
|
23
|
+
expect { Producer.new([],"client_id", Producer::OPTION_DEFAULTS.dup) }.not_to raise_error
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "sending messages" do
|
27
|
+
before(:each) do
|
28
|
+
@sync_producer = double('sync_producer').as_null_object
|
29
|
+
SyncProducer.stub!(:new).and_return(@sync_producer)
|
30
|
+
|
31
|
+
@producer = Producer.new([], "client_id", :type => :sync)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "turns MessagesToSend into Message objects" do
|
35
|
+
@sync_producer.should_receive(:send_messages).with([an_instance_of(Message)])
|
36
|
+
|
37
|
+
m = MessageToSend.new("topic", "value")
|
38
|
+
@producer.send_messages([m])
|
39
|
+
end
|
40
|
+
|
41
|
+
it "raises an ArgumentError if you try to send a single message" do
|
42
|
+
expect { @producer.send_messages(MessageToSend.new("topic", "value")) }.to raise_error(ArgumentError)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|