poseidon 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|