beaneater 0.2.2 → 0.3.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/CHANGELOG.md +5 -1
- data/lib/beaneater.rb +0 -1
- data/lib/beaneater/connection.rb +34 -26
- data/lib/beaneater/pool.rb +6 -6
- data/lib/beaneater/pool_command.rb +2 -2
- data/lib/beaneater/tube/record.rb +1 -1
- data/lib/beaneater/version.rb +1 -1
- data/test/connection_test.rb +21 -14
- data/test/pool_test.rb +18 -19
- data/test/prompt_regexp_test.rb +45 -0
- data/test/tube_test.rb +12 -1
- metadata +10 -2
data/CHANGELOG.md
CHANGED
data/lib/beaneater.rb
CHANGED
data/lib/beaneater/connection.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'yaml'
|
2
|
+
require 'socket'
|
2
3
|
|
3
4
|
module Beaneater
|
4
5
|
# Represents a connection to a beanstalkd instance.
|
@@ -16,9 +17,9 @@ module Beaneater
|
|
16
17
|
# @return [Integer] returns Beanstalkd server port
|
17
18
|
# @example
|
18
19
|
# @conn.port # => "11300"
|
19
|
-
# @!attribute
|
20
|
-
# @return [Net::
|
21
|
-
attr_reader :address, :host, :port, :
|
20
|
+
# @!attribute connection
|
21
|
+
# @return [Net::TCPSocket] returns connection object
|
22
|
+
attr_reader :address, :host, :port, :connection
|
22
23
|
|
23
24
|
# Default port value for beanstalk connection
|
24
25
|
DEFAULT_PORT = 11300
|
@@ -32,28 +33,26 @@ module Beaneater
|
|
32
33
|
#
|
33
34
|
def initialize(address)
|
34
35
|
@address = address
|
35
|
-
@
|
36
|
+
@connection = establish_connection
|
36
37
|
@mutex = Mutex.new
|
37
38
|
end
|
38
39
|
|
39
|
-
# Send commands to beanstalkd server via
|
40
|
+
# Send commands to beanstalkd server via connection.
|
40
41
|
#
|
42
|
+
# @param [Hash{String => String, Number}>] options Retained for compatibility
|
41
43
|
# @param [String] command Beanstalkd command
|
42
|
-
# @param [Hash{Symbol => String,Boolean}] options Settings for telnet
|
43
|
-
# @option options [Boolean] FailEOF raises EOF Exeception
|
44
|
-
# @option options [Integer] Timeout number of seconds before timeout
|
45
44
|
# @return [Array<Hash{String => String, Number}>] Beanstalkd command response
|
46
45
|
# @example
|
47
46
|
# @conn.transmit('bury 123')
|
48
47
|
#
|
49
|
-
def transmit(command, options={}
|
48
|
+
def transmit(command, options={})
|
50
49
|
@mutex.lock
|
51
|
-
if
|
50
|
+
if connection
|
52
51
|
command = command.force_encoding('ASCII-8BIT') if command.respond_to?(:force_encoding)
|
53
|
-
|
54
|
-
parse_response(command,
|
55
|
-
else # no
|
56
|
-
raise NotConnected, "Connection to beanstalk '#{@host}:#{@port}' is closed!" unless
|
52
|
+
connection.write(command+"\r\n")
|
53
|
+
parse_response(command, connection.gets)
|
54
|
+
else # no connection
|
55
|
+
raise NotConnected, "Connection to beanstalk '#{@host}:#{@port}' is closed!" unless connection
|
57
56
|
end
|
58
57
|
ensure
|
59
58
|
@mutex.unlock
|
@@ -65,8 +64,8 @@ module Beaneater
|
|
65
64
|
# @conn.close
|
66
65
|
#
|
67
66
|
def close
|
68
|
-
@
|
69
|
-
@
|
67
|
+
@connection.close
|
68
|
+
@connection = nil
|
70
69
|
end
|
71
70
|
|
72
71
|
# Returns string representation of job.
|
@@ -81,9 +80,9 @@ module Beaneater
|
|
81
80
|
|
82
81
|
protected
|
83
82
|
|
84
|
-
# Establish a
|
83
|
+
# Establish a connection based on beanstalk address.
|
85
84
|
#
|
86
|
-
# @return [Net::
|
85
|
+
# @return [Net::TCPSocket] connection for specified address.
|
87
86
|
# @raise [Beanstalk::NotConnected] Could not connect to specified beanstalkd instance.
|
88
87
|
# @example
|
89
88
|
# establish_connection('localhost:3005')
|
@@ -91,14 +90,15 @@ module Beaneater
|
|
91
90
|
def establish_connection
|
92
91
|
@match = address.split(':')
|
93
92
|
@host, @port = @match[0], Integer(@match[1] || DEFAULT_PORT)
|
94
|
-
|
93
|
+
TCPSocket.new @host, @port
|
95
94
|
rescue Errno::ECONNREFUSED => e
|
96
95
|
raise NotConnected, "Could not connect to '#{@host}:#{@port}'"
|
97
96
|
rescue Exception => ex
|
98
97
|
raise NotConnected, "#{ex.class}: #{ex}"
|
99
98
|
end
|
100
99
|
|
101
|
-
# Parses the
|
100
|
+
# Parses the response and returns the useful beanstalk response.
|
101
|
+
# Will read the body if one is indicated by the status.
|
102
102
|
#
|
103
103
|
# @param [String] cmd Beanstalk command transmitted
|
104
104
|
# @param [String] res Telnet command response
|
@@ -109,12 +109,19 @@ module Beaneater
|
|
109
109
|
# # => { :body => "FOO", :status => "DELETED", :id => 56, :connection => <Connection> }
|
110
110
|
#
|
111
111
|
def parse_response(cmd, res)
|
112
|
-
|
113
|
-
|
114
|
-
status
|
112
|
+
status = res.chomp
|
113
|
+
body_values = status.split(/\s/)
|
114
|
+
status = body_values[0]
|
115
115
|
raise UnexpectedResponse.from_status(status, cmd) if UnexpectedResponse::ERROR_STATES.include?(status)
|
116
|
-
|
117
|
-
|
116
|
+
body = nil
|
117
|
+
if ['OK','FOUND', 'RESERVED'].include?(status)
|
118
|
+
bytes_size = body_values[-1].to_i
|
119
|
+
raw_body = connection.read(bytes_size)
|
120
|
+
body = status == 'OK' ? YAML.load(raw_body) : config.job_parser.call(raw_body)
|
121
|
+
crlf = connection.read(2) # \r\n
|
122
|
+
raise ExpectedCRLFError if crlf != "\r\n"
|
123
|
+
end
|
124
|
+
id = body_values[1]
|
118
125
|
response = { :status => status, :body => body }
|
119
126
|
response[:id] = id if id
|
120
127
|
response[:connection] = self
|
@@ -127,5 +134,6 @@ module Beaneater
|
|
127
134
|
def config
|
128
135
|
Beaneater.configuration
|
129
136
|
end
|
137
|
+
|
130
138
|
end # Connection
|
131
|
-
end # Beaneater
|
139
|
+
end # Beaneater
|
data/lib/beaneater/pool.rb
CHANGED
@@ -52,8 +52,8 @@ module Beaneater
|
|
52
52
|
# Sends command to every beanstalkd server set in the pool.
|
53
53
|
#
|
54
54
|
# @param [String] command Beanstalkd command
|
55
|
-
# @param [Hash{String => String, Boolean}] options
|
56
|
-
# @param [Proc] block Block passed to
|
55
|
+
# @param [Hash{String => String, Boolean}] options socket connections options
|
56
|
+
# @param [Proc] block Block passed to socket connection during transmit
|
57
57
|
# @return [Array<Hash{String => String, Number}>] Beanstalkd command response from each instance
|
58
58
|
# @example
|
59
59
|
# @pool.transmit_to_all("stats")
|
@@ -75,8 +75,8 @@ module Beaneater
|
|
75
75
|
# Send command to each beanstalkd servers until getting response expected
|
76
76
|
#
|
77
77
|
# @param [String] command Beanstalkd command
|
78
|
-
# @param [Hash{String => String, Boolean}] options
|
79
|
-
# @param [Proc] block Block passed in
|
78
|
+
# @param [Hash{String => String, Boolean}] options socket connections options
|
79
|
+
# @param [Proc] block Block passed in socket connection object
|
80
80
|
# @return [Array<Hash{String => String, Number}>] Beanstalkd command response from the instance
|
81
81
|
# @example
|
82
82
|
# @pool.transmit_until_res('peek-ready', :status => "FOUND", &block)
|
@@ -99,8 +99,8 @@ module Beaneater
|
|
99
99
|
# Sends command to a random beanstalkd server in the pool.
|
100
100
|
#
|
101
101
|
# @param [String] command Beanstalkd command
|
102
|
-
# @param [Hash{String => String,Boolean}] options
|
103
|
-
# @param [Proc] block Block passed in
|
102
|
+
# @param [Hash{String => String,Boolean}] options socket connections options
|
103
|
+
# @param [Proc] block Block passed in socket connection object
|
104
104
|
# @return [Array<Hash{String => String, Number}>] Beanstalkd command response from the instance
|
105
105
|
# @example
|
106
106
|
# @pool.transmit_to_rand("stats", :match => /\n/)
|
@@ -17,9 +17,9 @@ module Beaneater
|
|
17
17
|
# Delegate to Pool#transmit_to_all and if needed will merge responses from beanstalkd.
|
18
18
|
#
|
19
19
|
# @param [String] body Beanstalkd command
|
20
|
-
# @param [Hash{String => String, Boolean}] options
|
20
|
+
# @param [Hash{String => String, Boolean}] options socket connections options
|
21
21
|
# @option options [Boolean] merge Ask for merging responses or not
|
22
|
-
# @param [Proc] block Block passed in
|
22
|
+
# @param [Proc] block Block passed in socket connection object
|
23
23
|
# @example
|
24
24
|
# @pool.transmit_to_all("stats")
|
25
25
|
#
|
@@ -36,7 +36,7 @@ module Beaneater
|
|
36
36
|
options = { :pri => config.default_put_pri, :delay => config.default_put_delay,
|
37
37
|
:ttr => config.default_put_ttr }.merge(options)
|
38
38
|
cmd_options = "#{options[:pri]} #{options[:delay]} #{options[:ttr]} #{body.bytesize}"
|
39
|
-
transmit_to_rand("put #{cmd_options}\n#{body}")
|
39
|
+
transmit_to_rand("put #{cmd_options}\r\n#{body}")
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
data/lib/beaneater/version.rb
CHANGED
data/test/connection_test.rb
CHANGED
@@ -16,11 +16,10 @@ describe Beaneater::Connection do
|
|
16
16
|
assert_equal 11300, @bc.port
|
17
17
|
end
|
18
18
|
|
19
|
-
it "should init
|
20
|
-
|
21
|
-
|
22
|
-
assert_equal
|
23
|
-
assert_equal 11300, telops["Port"]
|
19
|
+
it "should init connection" do
|
20
|
+
assert_kind_of TCPSocket, @bc.connection
|
21
|
+
assert_equal '127.0.0.1', @bc.connection.peeraddr[3]
|
22
|
+
assert_equal 11300, @bc.connection.peeraddr[1]
|
24
23
|
end
|
25
24
|
|
26
25
|
it "should raise on invalid connection" do
|
@@ -41,18 +40,26 @@ describe Beaneater::Connection do
|
|
41
40
|
end
|
42
41
|
|
43
42
|
it "should return id" do
|
44
|
-
|
45
|
-
res
|
46
|
-
assert_equal '254', res[:id]
|
43
|
+
res = @bc.transmit "put 0 0 100 1\r\nX"
|
44
|
+
assert res[:id]
|
47
45
|
assert_equal 'INSERTED', res[:status]
|
48
46
|
end
|
49
47
|
|
50
48
|
it "should support dashes in response" do
|
51
|
-
|
52
|
-
res = @bc.transmit 'bar'
|
49
|
+
res = @bc.transmit "use foo-bar\r\n"
|
53
50
|
assert_equal 'USING', res[:status]
|
54
51
|
assert_equal 'foo-bar', res[:id]
|
55
52
|
end
|
53
|
+
|
54
|
+
it "should pass crlf through without changing its length" do
|
55
|
+
res = @bc.transmit "put 0 0 100 2\r\n\r\n"
|
56
|
+
assert_equal 'INSERTED', res[:status]
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should handle *any* byte value without changing length" do
|
60
|
+
res = @bc.transmit "put 0 0 100 256\r\n"+(0..255).to_a.pack("c*")
|
61
|
+
assert_equal 'INSERTED', res[:status]
|
62
|
+
end
|
56
63
|
end # transmit
|
57
64
|
|
58
65
|
describe 'for #close' do
|
@@ -61,11 +68,11 @@ describe Beaneater::Connection do
|
|
61
68
|
@bc = Beaneater::Connection.new(@host)
|
62
69
|
end
|
63
70
|
|
64
|
-
it "should clear
|
65
|
-
assert_kind_of
|
71
|
+
it "should clear connection" do
|
72
|
+
assert_kind_of TCPSocket, @bc.connection
|
66
73
|
@bc.close
|
67
|
-
assert_nil @bc.
|
74
|
+
assert_nil @bc.connection
|
68
75
|
assert_raises(Beaneater::NotConnected) { @bc.transmit 'stats' }
|
69
76
|
end
|
70
77
|
end # close
|
71
|
-
end # Beaneater::Connection
|
78
|
+
end # Beaneater::Connection
|
data/test/pool_test.rb
CHANGED
@@ -19,10 +19,10 @@ describe Beaneater::Pool do
|
|
19
19
|
@host_num = '1.1.1.1:11303'
|
20
20
|
@hosts = [@host_string_port, @host_num_port, @host_string, @host_num]
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
22
|
+
TCPSocket.expects(:new).with('localhost',11301).once
|
23
|
+
TCPSocket.expects(:new).with('127.0.0.1',11302).once
|
24
|
+
TCPSocket.expects(:new).with('host.local',11300).once
|
25
|
+
TCPSocket.expects(:new).with('1.1.1.1',11303).once
|
26
26
|
|
27
27
|
@bp = Beaneater::Pool.new(@hosts)
|
28
28
|
end
|
@@ -110,7 +110,7 @@ describe Beaneater::Pool do
|
|
110
110
|
end
|
111
111
|
|
112
112
|
it "should return id" do
|
113
|
-
|
113
|
+
Beaneater::Connection.any_instance.expects(:transmit).with("foo",{}).returns({:id => "254", :status => "INSERTED"})
|
114
114
|
res = @bp.transmit_to_rand 'foo'
|
115
115
|
assert_equal '254', res[:id]
|
116
116
|
assert_equal 'INSERTED', res[:status]
|
@@ -139,31 +139,30 @@ describe Beaneater::Pool do
|
|
139
139
|
|
140
140
|
describe "for #safe_transmit" do
|
141
141
|
it "should retry 3 times for temporary failed connection" do
|
142
|
-
|
142
|
+
TCPSocket.any_instance.expects(:write).times(3)
|
143
|
+
TCPSocket.any_instance.expects(:gets).raises(Errno::ECONNRESET).then.
|
143
144
|
raises(Errno::ECONNRESET).then.returns('INSERTED 254').times(3)
|
144
|
-
res = @bp.transmit_to_rand
|
145
|
+
res = @bp.transmit_to_rand "put 0 0 10 2\r\nxy"
|
145
146
|
assert_equal '254', res[:id]
|
146
147
|
assert_equal 'INSERTED', res[:status]
|
147
148
|
end
|
148
149
|
|
149
|
-
it "should retry on fail 3 times for dead connection" do
|
150
|
-
Net::Telnet.any_instance.expects(:cmd).raises(EOFError).times(3)
|
151
|
-
assert_raises(Beaneater::NotConnected) { @bp.transmit_to_rand 'foo' }
|
152
|
-
end
|
153
|
-
|
154
150
|
it 'should raise proper exception for invalid status NOT_FOUND' do
|
155
|
-
|
151
|
+
TCPSocket.any_instance.expects(:write).once
|
152
|
+
TCPSocket.any_instance.expects(:gets).returns('NOT_FOUND')
|
156
153
|
assert_raises(Beaneater::NotFoundError) { @bp.transmit_to_rand 'foo' }
|
157
154
|
end
|
158
155
|
|
159
156
|
it 'should raise proper exception for invalid status BAD_FORMAT' do
|
160
|
-
|
157
|
+
TCPSocket.any_instance.expects(:write).once
|
158
|
+
TCPSocket.any_instance.expects(:gets).returns('BAD_FORMAT')
|
161
159
|
assert_raises(Beaneater::BadFormatError) { @bp.transmit_to_rand 'foo' }
|
162
160
|
end
|
163
161
|
|
164
162
|
it 'should raise proper exception for invalid status DEADLINE_SOON' do
|
165
|
-
|
166
|
-
|
163
|
+
TCPSocket.any_instance.expects(:write).once
|
164
|
+
TCPSocket.any_instance.expects(:gets).once.returns('DEADLINE_SOON')
|
165
|
+
assert_raises(Beaneater::DeadlineSoonError) { @bp.transmit_to_rand 'expecting deadline' }
|
167
166
|
end
|
168
167
|
end
|
169
168
|
|
@@ -172,10 +171,10 @@ describe Beaneater::Pool do
|
|
172
171
|
connection = @bp.connections.first
|
173
172
|
assert_equal 2, @bp.connections.size
|
174
173
|
assert_kind_of Beaneater::Connection, connection
|
175
|
-
assert_kind_of
|
174
|
+
assert_kind_of TCPSocket, connection.connection
|
176
175
|
@bp.close
|
177
176
|
assert_equal 0, @bp.connections.size
|
178
|
-
assert_nil connection.
|
177
|
+
assert_nil connection.connection
|
179
178
|
end
|
180
179
|
end # close
|
181
|
-
end # Beaneater::Pool
|
180
|
+
end # Beaneater::Pool
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# test/prompt_regexp_test.rb
|
2
|
+
|
3
|
+
require File.expand_path('../test_helper', __FILE__)
|
4
|
+
require 'socket'
|
5
|
+
|
6
|
+
describe "Reading from socket client" do
|
7
|
+
before do
|
8
|
+
@fake_port = 11301
|
9
|
+
@tube_name = 'tube.to.test'
|
10
|
+
|
11
|
+
@fake_server = Thread.start do
|
12
|
+
server = TCPServer.new(@fake_port)
|
13
|
+
loop do
|
14
|
+
IO.select([server])
|
15
|
+
client = server.accept_nonblock
|
16
|
+
while line = client.gets
|
17
|
+
case line
|
18
|
+
when /list-tubes-watched/i
|
19
|
+
client.print "OK #{7+@tube_name.size}\r\n---\n- #{@tube_name}\n\r\n"
|
20
|
+
when /watch #{@tube_name}/i
|
21
|
+
client.print "WATCHING 1\r\n"
|
22
|
+
when /reserve/i
|
23
|
+
client.print "RESERVED 17 25\r\n"
|
24
|
+
client.print "[first part]"
|
25
|
+
# Emulate network delay
|
26
|
+
sleep 0.5
|
27
|
+
client.print "[second part]\r\n"
|
28
|
+
else
|
29
|
+
client.print "ERROR\r\n"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should reserve job with full body' do
|
37
|
+
pool = Beaneater::Pool.new("localhost:#{@fake_port}")
|
38
|
+
job = pool.tubes[@tube_name].reserve
|
39
|
+
assert_equal '[first part][second part]', job.body
|
40
|
+
end
|
41
|
+
|
42
|
+
after do
|
43
|
+
@fake_server.kill
|
44
|
+
end
|
45
|
+
end
|
data/test/tube_test.rb
CHANGED
@@ -83,6 +83,17 @@ describe Beaneater::Tube do
|
|
83
83
|
assert_equal 'hi', JSON.parse(@tube.peek(:ready).body)['message']
|
84
84
|
end
|
85
85
|
|
86
|
+
it "supports passing crlf through" do
|
87
|
+
@tube.put("\r\n")
|
88
|
+
assert_equal "\r\n", @tube.peek(:ready).body
|
89
|
+
end
|
90
|
+
|
91
|
+
it "supports passing any byte value through" do
|
92
|
+
bytes = (0..255).to_a.pack("c*")
|
93
|
+
@tube.put(bytes)
|
94
|
+
assert_equal bytes, @tube.peek(:ready).body
|
95
|
+
end
|
96
|
+
|
86
97
|
it "should support custom parser" do
|
87
98
|
Beaneater.configure.job_parser = lambda { |b| JSON.parse(b) }
|
88
99
|
json = '{"message":"hi"}'
|
@@ -195,4 +206,4 @@ describe Beaneater::Tube do
|
|
195
206
|
after do
|
196
207
|
cleanup_tubes!(['baz'])
|
197
208
|
end
|
198
|
-
end # Beaneater::Tubes
|
209
|
+
end # Beaneater::Tubes
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: beaneater
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2013-01-23 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: minitest
|
@@ -147,6 +147,7 @@ files:
|
|
147
147
|
- test/jobs_test.rb
|
148
148
|
- test/pool_command_test.rb
|
149
149
|
- test/pool_test.rb
|
150
|
+
- test/prompt_regexp_test.rb
|
150
151
|
- test/stat_struct_test.rb
|
151
152
|
- test/stats_test.rb
|
152
153
|
- test/test_helper.rb
|
@@ -164,12 +165,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
164
165
|
- - ! '>='
|
165
166
|
- !ruby/object:Gem::Version
|
166
167
|
version: '0'
|
168
|
+
segments:
|
169
|
+
- 0
|
170
|
+
hash: -394754542791428871
|
167
171
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
168
172
|
none: false
|
169
173
|
requirements:
|
170
174
|
- - ! '>='
|
171
175
|
- !ruby/object:Gem::Version
|
172
176
|
version: '0'
|
177
|
+
segments:
|
178
|
+
- 0
|
179
|
+
hash: -394754542791428871
|
173
180
|
requirements: []
|
174
181
|
rubyforge_project:
|
175
182
|
rubygems_version: 1.8.24
|
@@ -184,6 +191,7 @@ test_files:
|
|
184
191
|
- test/jobs_test.rb
|
185
192
|
- test/pool_command_test.rb
|
186
193
|
- test/pool_test.rb
|
194
|
+
- test/prompt_regexp_test.rb
|
187
195
|
- test/stat_struct_test.rb
|
188
196
|
- test/stats_test.rb
|
189
197
|
- test/test_helper.rb
|