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.
@@ -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