beaneater 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,10 @@
1
1
  # CHANGELOG for Beaneater
2
2
 
3
- ## 0.2.3 (Unreleased)
3
+ ## 0.3.1 (Unreleased)
4
+
5
+ ## 0.3.0 (Jan 23 2013)
6
+
7
+ * Replace Telnet with tcpsocket thanks @vidarh
4
8
 
5
9
  ## 0.2.2 (Dec 2 2012)
6
10
 
@@ -1,4 +1,3 @@
1
- require 'net/telnet'
2
1
  require 'thread' unless defined?(Mutex)
3
2
 
4
3
  %w(version configuration errors pool_command pool connection stats tube job).each do |f|
@@ -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 telnet_connection
20
- # @return [Net::Telnet] returns Telnet connection object
21
- attr_reader :address, :host, :port, :telnet_connection
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
- @telnet_connection = establish_connection
36
+ @connection = establish_connection
36
37
  @mutex = Mutex.new
37
38
  end
38
39
 
39
- # Send commands to beanstalkd server via telnet_connection.
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={}, &block)
48
+ def transmit(command, options={})
50
49
  @mutex.lock
51
- if telnet_connection
50
+ if connection
52
51
  command = command.force_encoding('ASCII-8BIT') if command.respond_to?(:force_encoding)
53
- options.merge!("String" => command, "FailEOF" => true, "Timeout" => false)
54
- parse_response(command, telnet_connection.cmd(options, &block))
55
- else # no telnet_connection
56
- raise NotConnected, "Connection to beanstalk '#{@host}:#{@port}' is closed!" unless telnet_connection
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
- @telnet_connection.close
69
- @telnet_connection = nil
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 telnet connection based on beanstalk address.
83
+ # Establish a connection based on beanstalk address.
85
84
  #
86
- # @return [Net::Telnet] telnet connection for specified address.
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
- Net::Telnet.new('Host' => @host, "Port" => @port, "Prompt" => /\n/)
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 telnet response and returns the useful beanstalk response.
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
- res_lines = res.split(/\r?\n/)
113
- status = res_lines.first
114
- status, id = status.split(/\s/)
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
- raw_body = res_lines[1..-1].join("\n")
117
- body = ['FOUND', 'RESERVED'].include?(status) ? config.job_parser.call(raw_body) : YAML.load(raw_body)
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
@@ -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 telnet connections options
56
- # @param [Proc] block Block passed to telnet connection during transmit
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 telnet connections options
79
- # @param [Proc] block Block passed in telnet connection object
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 telnet connections options
103
- # @param [Proc] block Block passed in telnet connection object
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 telnet connections 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 telnet connection object
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
 
@@ -1,4 +1,4 @@
1
1
  module Beaneater
2
2
  # Current version of gem.
3
- VERSION = "0.2.2"
3
+ VERSION = "0.3.0"
4
4
  end
@@ -16,11 +16,10 @@ describe Beaneater::Connection do
16
16
  assert_equal 11300, @bc.port
17
17
  end
18
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"]
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
- 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]
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
- Net::Telnet.any_instance.expects(:cmd).with(has_entries('String' => 'bar')).returns('USING foo-bar')
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 telnet connection" do
65
- assert_kind_of Net::Telnet, @bc.telnet_connection
71
+ it "should clear connection" do
72
+ assert_kind_of TCPSocket, @bc.connection
66
73
  @bc.close
67
- assert_nil @bc.telnet_connection
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
@@ -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
- Net::Telnet.expects(:new).with('Host' => 'localhost', "Port" => 11301, "Prompt" => /\n/).once
23
- Net::Telnet.expects(:new).with('Host' => '127.0.0.1', "Port" => 11302, "Prompt" => /\n/).once
24
- Net::Telnet.expects(:new).with('Host' => 'host.local', "Port" => 11300, "Prompt" => /\n/).once
25
- Net::Telnet.expects(:new).with('Host' => '1.1.1.1', "Port" => 11303, "Prompt" => /\n/).once
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
- Net::Telnet.any_instance.expects(:cmd).with(has_entries('String' => 'foo')).returns('INSERTED 254')
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
- Net::Telnet.any_instance.expects(:cmd).raises(EOFError).then.
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 'foo'
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
- Net::Telnet.any_instance.expects(:cmd).returns('NOT_FOUND')
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
- Net::Telnet.any_instance.expects(:cmd).returns('BAD_FORMAT')
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
- Net::Telnet.any_instance.expects(:cmd).returns('DEADLINE_SOON')
166
- assert_raises(Beaneater::DeadlineSoonError) { @bp.transmit_to_rand 'foo' }
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 Net::Telnet, connection.telnet_connection
174
+ assert_kind_of TCPSocket, connection.connection
176
175
  @bp.close
177
176
  assert_equal 0, @bp.connections.size
178
- assert_nil connection.telnet_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
@@ -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.2.2
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: 2012-12-02 00:00:00.000000000 Z
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