beaneater 0.1.0
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 +17 -0
- data/.yardopts +8 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +22 -0
- data/README.md +399 -0
- data/REF +23 -0
- data/Rakefile +23 -0
- data/TODO +2 -0
- data/beaneater.gemspec +24 -0
- data/examples/demo.rb +96 -0
- data/lib/beaneater.rb +10 -0
- data/lib/beaneater/connection.rb +110 -0
- data/lib/beaneater/errors.rb +73 -0
- data/lib/beaneater/job.rb +2 -0
- data/lib/beaneater/job/collection.rb +91 -0
- data/lib/beaneater/job/record.rb +174 -0
- data/lib/beaneater/pool.rb +141 -0
- data/lib/beaneater/pool_command.rb +71 -0
- data/lib/beaneater/stats.rb +55 -0
- data/lib/beaneater/stats/fast_struct.rb +96 -0
- data/lib/beaneater/stats/stat_struct.rb +39 -0
- data/lib/beaneater/tube.rb +2 -0
- data/lib/beaneater/tube/collection.rb +134 -0
- data/lib/beaneater/tube/record.rb +158 -0
- data/lib/beaneater/version.rb +4 -0
- data/test/beaneater_test.rb +115 -0
- data/test/connection_test.rb +64 -0
- data/test/errors_test.rb +26 -0
- data/test/job_test.rb +213 -0
- data/test/jobs_test.rb +107 -0
- data/test/pool_command_test.rb +68 -0
- data/test/pool_test.rb +154 -0
- data/test/stat_struct_test.rb +41 -0
- data/test/stats_test.rb +42 -0
- data/test/test_helper.rb +21 -0
- data/test/tube_test.rb +164 -0
- data/test/tubes_test.rb +153 -0
- metadata +181 -0
@@ -0,0 +1,64 @@
|
|
1
|
+
# test/connection_test.rb
|
2
|
+
|
3
|
+
require File.expand_path('../test_helper', __FILE__)
|
4
|
+
|
5
|
+
describe Beaneater::Connection do
|
6
|
+
|
7
|
+
describe 'for #new' do
|
8
|
+
before do
|
9
|
+
@host = 'localhost'
|
10
|
+
@bc = Beaneater::Connection.new(@host)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should store address, host and port" do
|
14
|
+
assert_equal 'localhost', @bc.address
|
15
|
+
assert_equal 'localhost', @bc.host
|
16
|
+
assert_equal 11300, @bc.port
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should init telnet connection" do
|
20
|
+
telops = @bc.telnet_connection.instance_variable_get(:@options)
|
21
|
+
assert_kind_of Net::Telnet, @bc.telnet_connection
|
22
|
+
assert_equal 'localhost', telops["Host"]
|
23
|
+
assert_equal 11300, telops["Port"]
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should raise on invalid connection" do
|
27
|
+
assert_raises(Beaneater::NotConnected) { Beaneater::Connection.new("localhost:8544") }
|
28
|
+
end
|
29
|
+
end # new
|
30
|
+
|
31
|
+
describe 'for #transmit' do
|
32
|
+
before do
|
33
|
+
@host = 'localhost'
|
34
|
+
@bc = Beaneater::Connection.new(@host)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should return yaml loaded response" do
|
38
|
+
res = @bc.transmit 'stats'
|
39
|
+
refute_nil res[:body]['current-connections']
|
40
|
+
assert_equal 'OK', res[:status]
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should return id" do
|
44
|
+
Net::Telnet.any_instance.expects(:cmd).with(has_entries('String' => 'foo')).returns('INSERTED 254')
|
45
|
+
res = @bc.transmit 'foo'
|
46
|
+
assert_equal '254', res[:id]
|
47
|
+
assert_equal 'INSERTED', res[:status]
|
48
|
+
end
|
49
|
+
end # transmit
|
50
|
+
|
51
|
+
describe 'for #close' do
|
52
|
+
before do
|
53
|
+
@host = 'localhost'
|
54
|
+
@bc = Beaneater::Connection.new(@host)
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should clear telnet connection" do
|
58
|
+
assert_kind_of Net::Telnet, @bc.telnet_connection
|
59
|
+
@bc.close
|
60
|
+
assert_nil @bc.telnet_connection
|
61
|
+
assert_raises(Beaneater::NotConnected) { @bc.transmit 'stats' }
|
62
|
+
end
|
63
|
+
end # close
|
64
|
+
end # Beaneater::Connection
|
data/test/errors_test.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# test/connection_test.rb
|
2
|
+
|
3
|
+
require File.expand_path('../test_helper', __FILE__)
|
4
|
+
|
5
|
+
describe "Beaneater::Errors" do
|
6
|
+
it 'should raise proper exception for invalid status NOT_FOUND' do
|
7
|
+
@klazz = Beaneater::UnexpectedResponse.from_status("NOT_FOUND", "job-stats -1")
|
8
|
+
assert_kind_of(Beaneater::NotFoundError, @klazz)
|
9
|
+
assert_equal 'job-stats -1', @klazz.cmd
|
10
|
+
assert_equal 'NOT_FOUND', @klazz.status
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should raise proper exception for invalid status BAD_FORMAT' do
|
14
|
+
@klazz = Beaneater::UnexpectedResponse.from_status("BAD_FORMAT", "FAKE")
|
15
|
+
assert_kind_of(Beaneater::BadFormatError, @klazz)
|
16
|
+
assert_equal 'FAKE', @klazz.cmd
|
17
|
+
assert_equal 'BAD_FORMAT', @klazz.status
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should raise proper exception for invalid status DEADLINE_SOON' do
|
21
|
+
@klazz = Beaneater::UnexpectedResponse.from_status("DEADLINE_SOON", "reserve 0")
|
22
|
+
assert_kind_of(Beaneater::DeadlineSoonError, @klazz)
|
23
|
+
assert_equal 'reserve 0', @klazz.cmd
|
24
|
+
assert_equal 'DEADLINE_SOON', @klazz.status
|
25
|
+
end
|
26
|
+
end
|
data/test/job_test.rb
ADDED
@@ -0,0 +1,213 @@
|
|
1
|
+
# test/connection_test.rb
|
2
|
+
|
3
|
+
require File.expand_path('../test_helper', __FILE__)
|
4
|
+
|
5
|
+
describe Beaneater::Job do
|
6
|
+
before do
|
7
|
+
@pool = Beaneater::Pool.new(['localhost'])
|
8
|
+
@tube = @pool.tubes.find 'tube'
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "for #bury" do
|
12
|
+
before do
|
13
|
+
@time = Time.now.to_i
|
14
|
+
@tube.put "foo bury #{@time}", :pri => 5
|
15
|
+
end
|
16
|
+
|
17
|
+
it("should be buried with same pri") do
|
18
|
+
job = @tube.reserve
|
19
|
+
assert_equal "foo bury #{@time}", job.body
|
20
|
+
assert_equal 'reserved', job.stats.state
|
21
|
+
job.bury
|
22
|
+
assert_equal 'buried', job.stats.state
|
23
|
+
assert_equal 5, job.stats.pri
|
24
|
+
assert_equal "foo bury #{@time}", @tube.peek(:buried).body
|
25
|
+
end
|
26
|
+
|
27
|
+
it("should be released with new pri") do
|
28
|
+
job = @tube.reserve
|
29
|
+
assert_equal "foo bury #{@time}", job.body
|
30
|
+
assert_equal 'reserved', job.stats.state
|
31
|
+
job.bury(:pri => 10)
|
32
|
+
assert_equal 'buried', job.stats.state
|
33
|
+
assert_equal 10, job.stats.pri
|
34
|
+
assert_equal "foo bury #{@time}", @tube.peek(:buried).body
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should not bury if not reserved" do
|
38
|
+
job = @tube.peek(:ready)
|
39
|
+
assert_raises(Beaneater::JobNotReserved) { job.bury }
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should not bury if reserved and deleted" do
|
43
|
+
job = @tube.reserve
|
44
|
+
job.delete
|
45
|
+
assert_equal false, job.reserved
|
46
|
+
assert_raises(Beaneater::NotFoundError) { job.bury }
|
47
|
+
end
|
48
|
+
end # bury
|
49
|
+
|
50
|
+
describe "for #release" do
|
51
|
+
before do
|
52
|
+
@time = Time.now.to_i
|
53
|
+
@tube.put "foo release #{@time}", :pri => 5
|
54
|
+
end
|
55
|
+
|
56
|
+
it("should be released with same pri") do
|
57
|
+
job = @tube.reserve
|
58
|
+
assert_equal "foo release #{@time}", job.body
|
59
|
+
assert_equal 'reserved', job.stats.state
|
60
|
+
job.release
|
61
|
+
assert_equal 'ready', job.stats.state
|
62
|
+
assert_equal 5, job.stats.pri
|
63
|
+
assert_equal 0, job.stats.delay
|
64
|
+
end
|
65
|
+
|
66
|
+
it("should be released with new pri") do
|
67
|
+
job = @tube.reserve
|
68
|
+
assert_equal "foo release #{@time}", job.body
|
69
|
+
assert_equal 'reserved', job.stats.state
|
70
|
+
job.release :pri => 10, :delay => 2
|
71
|
+
assert_equal 'delayed', job.stats.state
|
72
|
+
assert_equal 10, job.stats.pri
|
73
|
+
assert_equal 2, job.stats.delay
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should not released if not reserved" do
|
77
|
+
job = @tube.peek(:ready)
|
78
|
+
assert_raises(Beaneater::JobNotReserved) { job.release }
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should not release if not reserved and buried" do
|
82
|
+
job = @tube.reserve
|
83
|
+
job.bury
|
84
|
+
assert_raises(Beaneater::JobNotReserved) { job.release }
|
85
|
+
end
|
86
|
+
end # release
|
87
|
+
|
88
|
+
describe "for #delete" do
|
89
|
+
before do
|
90
|
+
@tube.put 'foo'
|
91
|
+
end
|
92
|
+
|
93
|
+
it("should deletable") do
|
94
|
+
job = @tube.peek(:ready)
|
95
|
+
assert_equal 'foo', job.body
|
96
|
+
job.delete
|
97
|
+
assert_nil @tube.peek(:ready)
|
98
|
+
end
|
99
|
+
end # delete
|
100
|
+
|
101
|
+
describe "for #touch" do
|
102
|
+
before do
|
103
|
+
@tube.put 'foo touch', :ttr => 1
|
104
|
+
end
|
105
|
+
|
106
|
+
it("should be toucheable") do
|
107
|
+
job = @tube.reserve
|
108
|
+
assert_equal 'foo touch', job.body
|
109
|
+
job.touch
|
110
|
+
assert_equal 1, job.stats.reserves
|
111
|
+
job.delete
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should not touch if not reserved" do
|
115
|
+
job = @tube.peek(:ready)
|
116
|
+
assert_raises(Beaneater::JobNotReserved) { job.touch }
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should not touch if not reserved and released" do
|
120
|
+
job = @tube.reserve
|
121
|
+
job.release
|
122
|
+
assert_raises(Beaneater::JobNotReserved) { job.touch }
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should not touch if reserved and deleted" do
|
126
|
+
job = @tube.reserve
|
127
|
+
job.delete
|
128
|
+
assert_raises(Beaneater::NotFoundError) { job.touch }
|
129
|
+
end
|
130
|
+
end # touch
|
131
|
+
|
132
|
+
describe "for #kick" do
|
133
|
+
before do
|
134
|
+
@tube.put 'foo touch', :ttr => 1
|
135
|
+
end
|
136
|
+
|
137
|
+
it("should be toucheable") do
|
138
|
+
job = @tube.reserve
|
139
|
+
assert_equal 'foo touch', job.body
|
140
|
+
job.bury
|
141
|
+
assert_equal 1, @tube.stats.current_jobs_buried
|
142
|
+
if @pool.stats.version > 1.7
|
143
|
+
job.kick
|
144
|
+
assert_equal 0, @tube.stats.current_jobs_buried
|
145
|
+
assert_equal 1, @tube.stats.current_jobs_ready
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end # kick
|
149
|
+
|
150
|
+
describe "for #stats" do
|
151
|
+
before do
|
152
|
+
@tube.put 'foo'
|
153
|
+
@job = @tube.peek(:ready)
|
154
|
+
end
|
155
|
+
|
156
|
+
it("should have stats") do
|
157
|
+
assert_equal 'tube', @job.stats['tube']
|
158
|
+
assert_equal 'ready', @job.stats.state
|
159
|
+
end
|
160
|
+
|
161
|
+
it "should return nil for deleted job with no stats" do
|
162
|
+
@job.delete
|
163
|
+
assert_raises(Beaneater::NotFoundError) { @job.stats }
|
164
|
+
end
|
165
|
+
end # stats
|
166
|
+
|
167
|
+
describe "for #reserved?" do
|
168
|
+
before do
|
169
|
+
@tube.put 'foo'
|
170
|
+
@job = @tube.peek(:ready)
|
171
|
+
end
|
172
|
+
|
173
|
+
it("should have stats") do
|
174
|
+
assert_equal false, @job.reserved?
|
175
|
+
job = @tube.reserve
|
176
|
+
assert_equal job.id, @job.id
|
177
|
+
assert_equal true, @job.reserved?
|
178
|
+
@job.delete
|
179
|
+
assert_raises(Beaneater::NotFoundError) { @job.reserved? }
|
180
|
+
end
|
181
|
+
end # reserved?
|
182
|
+
|
183
|
+
describe "for #exists?" do
|
184
|
+
before do
|
185
|
+
@tube.put 'foo'
|
186
|
+
@job = @tube.peek(:ready)
|
187
|
+
end
|
188
|
+
|
189
|
+
it("should exists") { assert @job.exists? }
|
190
|
+
|
191
|
+
it "should not exist" do
|
192
|
+
@job.delete
|
193
|
+
assert !@job.exists?
|
194
|
+
end
|
195
|
+
end # exists?
|
196
|
+
|
197
|
+
describe "for #tube" do
|
198
|
+
before do
|
199
|
+
@tube.put 'bar'
|
200
|
+
@job = @tube.peek(:ready)
|
201
|
+
end
|
202
|
+
|
203
|
+
it("should have stats") do
|
204
|
+
job = @tube.reserve
|
205
|
+
assert_equal @tube.name, job.tube
|
206
|
+
job.release
|
207
|
+
end
|
208
|
+
end # tube
|
209
|
+
|
210
|
+
after do
|
211
|
+
cleanup_tubes!(['tube'])
|
212
|
+
end
|
213
|
+
end # Beaneater::Tubes
|
data/test/jobs_test.rb
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
# test/connection_test.rb
|
2
|
+
|
3
|
+
require File.expand_path('../test_helper', __FILE__)
|
4
|
+
|
5
|
+
describe Beaneater::Jobs do
|
6
|
+
before do
|
7
|
+
@pool = Beaneater::Pool.new(['localhost'])
|
8
|
+
@jobs = Beaneater::Jobs.new(@pool)
|
9
|
+
@tube = @pool.tubes.find('baz')
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "for #find" do
|
13
|
+
before do
|
14
|
+
@time = Time.now.to_i
|
15
|
+
@tube.put("foo find #{@time}")
|
16
|
+
@job = @tube.peek(:ready)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should return job from id" do
|
20
|
+
assert_equal "foo find #{@time}", @jobs.find(@job.id).body
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should return job using peek" do
|
24
|
+
assert_equal "foo find #{@time}", @jobs.find(@job.id).body
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should return job using hash syntax" do
|
28
|
+
assert_equal "foo find #{@time}", @jobs.find(@job.id).body
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should return nil for invalid id" do
|
32
|
+
assert_nil @jobs.find(-1)
|
33
|
+
end
|
34
|
+
end # find
|
35
|
+
|
36
|
+
describe "for #register!" do
|
37
|
+
before do
|
38
|
+
$foo = 0
|
39
|
+
@jobs.register('tube', :retry_on => [Timeout::Error]) do |job|
|
40
|
+
$foo += 1
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should store processor" do
|
45
|
+
assert_equal 'tube', @jobs.processors.keys.first
|
46
|
+
assert_equal [Timeout::Error], @jobs.processors.values.first[:retry_on]
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should store block for 'tube'" do
|
50
|
+
@jobs.processors['tube'][:block].call nil
|
51
|
+
assert_equal 1, $foo
|
52
|
+
end
|
53
|
+
end # register!
|
54
|
+
|
55
|
+
describe "for process!" do
|
56
|
+
before do
|
57
|
+
$foo = []
|
58
|
+
|
59
|
+
@jobs.register('tube_success', :retry_on => [Timeout::Error]) do |job|
|
60
|
+
# p job.body
|
61
|
+
$foo << job.body
|
62
|
+
raise Beaneater::AbortProcessingError if job.body =~ /abort/
|
63
|
+
end
|
64
|
+
|
65
|
+
@jobs.register('tube_release', :retry_on => [Timeout::Error], :max_retries => 2) do |job|
|
66
|
+
$foo << job.body
|
67
|
+
raise Timeout::Error
|
68
|
+
end
|
69
|
+
|
70
|
+
@jobs.register('tube_buried') do |job|
|
71
|
+
$foo << job.body
|
72
|
+
raise RuntimeError
|
73
|
+
end
|
74
|
+
|
75
|
+
cleanup_tubes!(['tube_success', 'tube_release', 'tube_buried'])
|
76
|
+
|
77
|
+
@pool.tubes.find('tube_success').put("success abort", :pri => 2**31 + 1)
|
78
|
+
@pool.tubes.find('tube_success').put("success 2", :pri => 1)
|
79
|
+
@pool.tubes.find('tube_release').put("released")
|
80
|
+
@pool.tubes.find('tube_buried').put("buried")
|
81
|
+
|
82
|
+
@jobs.process!(:release_delay => 0)
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should process all jobs" do
|
86
|
+
assert_equal ['success 2', 'released', 'released', 'released', 'buried', 'success abort'], $foo
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should clear successful_jobs" do
|
90
|
+
assert_equal 0, @pool.tubes.find('tube_success').stats.current_jobs_ready
|
91
|
+
assert_equal 1, @pool.tubes.find('tube_success').stats.current_jobs_buried
|
92
|
+
assert_equal 0, @pool.tubes.find('tube_success').stats.current_jobs_reserved
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should retry release jobs 2 times" do
|
96
|
+
assert_equal 2, @pool.tubes.find('tube_release').peek(:buried).stats.releases
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should bury unexpected exception" do
|
100
|
+
assert_equal 1, @pool.tubes.find('tube_buried').stats.current_jobs_buried
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
after do
|
105
|
+
cleanup_tubes!(['baz', 'tube_success', 'tube_release', 'tube_buried'])
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# test/connection_test.rb
|
2
|
+
|
3
|
+
require File.expand_path('../test_helper', __FILE__)
|
4
|
+
|
5
|
+
describe Beaneater::PoolCommand do
|
6
|
+
|
7
|
+
describe 'for #new' do
|
8
|
+
before do
|
9
|
+
@pool = stub
|
10
|
+
@command = Beaneater::PoolCommand.new(@pool)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should store pool" do
|
14
|
+
assert_equal @pool, @command.pool
|
15
|
+
end
|
16
|
+
end #new
|
17
|
+
|
18
|
+
describe 'for #transmit_to_all' do
|
19
|
+
describe 'for regular command' do
|
20
|
+
before do
|
21
|
+
@pool = stub(:transmit_to_all => "OK")
|
22
|
+
@command = Beaneater::PoolCommand.new(@pool)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "can run regular command" do
|
26
|
+
assert_equal "OK", @command.transmit_to_all("foo")
|
27
|
+
end
|
28
|
+
end # regular command
|
29
|
+
|
30
|
+
describe 'for merged command' do
|
31
|
+
before do
|
32
|
+
@pool = stub(:transmit_to_all => [{ :body => { 'x' => 1, 'version' => 1.1 }}, {:body => { 'x' => 3,'version' => 1.2 }}])
|
33
|
+
@command = Beaneater::PoolCommand.new(@pool)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "can run merge command" do
|
37
|
+
cmd = @command.transmit_to_all("bar", :merge => true)
|
38
|
+
assert_equal 4, cmd[:body]['x']
|
39
|
+
assert_equal Set[1.1, 1.2], cmd[:body]['version']
|
40
|
+
end
|
41
|
+
end # merged command
|
42
|
+
end # transmit_to_all
|
43
|
+
|
44
|
+
describe 'for #method_missing' do
|
45
|
+
describe '#transmit_to_rand' do
|
46
|
+
before do
|
47
|
+
@pool = stub
|
48
|
+
@pool.expects(:transmit_to_rand).with('foo').returns('OK')
|
49
|
+
@command = Beaneater::PoolCommand.new(@pool)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'delegates to connection' do
|
53
|
+
assert_equal 'OK', @command.transmit_to_rand('foo')
|
54
|
+
end
|
55
|
+
end # transmit_to_rand
|
56
|
+
|
57
|
+
describe 'invalid method' do
|
58
|
+
before do
|
59
|
+
@pool = stub
|
60
|
+
@command = Beaneater::PoolCommand.new(@pool)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'raises no method error' do
|
64
|
+
assert_raises(NoMethodError) { @command.foo('foo') }
|
65
|
+
end
|
66
|
+
end # transmit_to_rand
|
67
|
+
end
|
68
|
+
end # Beaneater::PoolCommand
|