beetle 0.3.0.rc.3 → 0.3.0.rc.4

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/Rakefile CHANGED
@@ -1,6 +1,8 @@
1
1
  require 'rake'
2
2
  require 'rake/testtask'
3
3
  require 'rcov/rcovtask'
4
+ require 'bundler/gem_tasks'
5
+
4
6
  # rake 0.9.2 hack to supress deprecation warnings caused by cucumber
5
7
  include Rake::DSL if RAKEVERSION >= "0.9"
6
8
  require 'cucumber/rake/task'
@@ -30,7 +32,6 @@ namespace :test do
30
32
  end if RUBY_PLATFORM =~ /darwin/
31
33
  end
32
34
 
33
-
34
35
  namespace :beetle do
35
36
  task :test do
36
37
  Beetle::Client.new.test
@@ -108,8 +109,3 @@ Rake::RDocTask.new do |rdoc|
108
109
  rdoc.rdoc_files.include('MIT-LICENSE')
109
110
  rdoc.rdoc_files.include('lib/**/*.rb')
110
111
  end
111
-
112
- desc "build the beetle gem"
113
- task :build do
114
- system("gem build beetle.gemspec")
115
- end
data/beetle.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "beetle"
3
- s.version = "0.3.0.rc.3"
3
+ s.version = "0.3.0.rc.4"
4
4
  s.required_rubygems_version = ">= 1.3.7"
5
5
  s.authors = ["Stefan Kaes", "Pascal Friederich", "Ali Jelveh", "Sebastian Roebke"]
6
6
  s.date = Time.now.strftime('%Y-%m-%d')
@@ -29,10 +29,12 @@ Gem::Specification.new do |s|
29
29
 
30
30
  s.specification_version = 3
31
31
  s.add_runtime_dependency("uuid4r", [">= 0.1.2"])
32
- s.add_runtime_dependency("bunny", ["~> 0.7.1"])
32
+ s.add_runtime_dependency("bunny", ["= 0.7.8"])
33
33
  s.add_runtime_dependency("redis", ["= 2.2.0"])
34
34
  s.add_runtime_dependency("hiredis", ["= 0.3.2"])
35
- s.add_runtime_dependency("amqp", ["~> 0.6.7"])
35
+ s.add_runtime_dependency("amq-client", ["= 0.8.3"])
36
+ s.add_runtime_dependency("amq-protocol", ["= 0.8.1"])
37
+ s.add_runtime_dependency("amqp", ["= 0.8.0"])
36
38
  s.add_runtime_dependency("activesupport", [">= 2.3.4"])
37
39
  s.add_runtime_dependency("daemons", [">= 1.0.10"])
38
40
  s.add_development_dependency("rake", [">= 0.8.7"])
@@ -0,0 +1,28 @@
1
+ # nonexistent_server.rb
2
+ # this example shows what happens when you try connect to a nonexistent server
3
+ #
4
+ # start it with ruby nonexistent_server.rb
5
+
6
+ require "rubygems"
7
+ require File.expand_path("../lib/beetle", File.dirname(__FILE__))
8
+
9
+ # set Beetle log level to info, less noisy than debug
10
+ Beetle.config.logger.level = Logger::INFO
11
+
12
+ Beetle.config.servers = "unknown.railsexpress.de:5672"
13
+
14
+ # setup client
15
+ client = Beetle::Client.new
16
+ client.register_queue(:test)
17
+ client.register_message(:test)
18
+
19
+ # register our handler to the message, check out the message.rb for more stuff you can get from the message object
20
+ client.register_handler(:test) {|message| puts "got message: #{message.data}"}
21
+
22
+ # start listening
23
+ # this starts the event machine event loop using EM.run
24
+ # the block passed to listen will be yielded as the last step of the setup process
25
+ client.listen do
26
+ EM.add_timer(10) { client.stop_listening }
27
+ end
28
+
@@ -58,7 +58,7 @@ end
58
58
  # this starts the event machine event loop using EM.run
59
59
  # the block passed to listen will be yielded as the last step of the setup process
60
60
  client.listen do
61
- EM.add_timer(0.1) { client.stop_listening }
61
+ EM.add_timer(0.2) { client.stop_listening }
62
62
  end
63
63
 
64
64
  puts "Received #{k} test messages"
data/examples/rpc.rb CHANGED
@@ -3,7 +3,7 @@ require "rubygems"
3
3
  require File.expand_path(File.dirname(__FILE__)+"/../lib/beetle")
4
4
 
5
5
  # suppress debug messages
6
- Beetle.config.logger.level = Logger::DEBUG
6
+ Beetle.config.logger.level = Logger::INFO
7
7
  Beetle.config.servers = "localhost:5672, localhost:5673"
8
8
  # instantiate a client
9
9
 
@@ -28,12 +28,12 @@ if ARGV.include?("--server")
28
28
  trap("INT") { puts "stopped echo server"; client.stop_listening }
29
29
  end
30
30
  else
31
- n = 100
31
+ n = 10000
32
32
  ms = Benchmark.ms do
33
33
  n.times do |i|
34
34
  content = "Hello #{i}"
35
35
  # puts "performing RPC with message content '#{content}'"
36
- status, result = client.rpc(:echo, content)
36
+ status, result = client.rpc(:echo, content, :persistent => false)
37
37
  # puts "status #{status}"
38
38
  # puts "result #{result}"
39
39
  # puts
data/lib/beetle/base.rb CHANGED
@@ -33,6 +33,10 @@ module Beetle
33
33
  @server = s
34
34
  end
35
35
 
36
+ def server_from_settings(settings)
37
+ settings.values_at(:host,:port).join(':')
38
+ end
39
+
36
40
  def each_server
37
41
  @servers.each { |s| set_current_server(s); yield }
38
42
  end
@@ -73,9 +73,10 @@ module Beetle
73
73
 
74
74
  # extracts various values form the AMQP header properties
75
75
  def decode #:nodoc:
76
- amqp_headers = header.properties
76
+ # p header.attributes
77
+ amqp_headers = header.attributes
77
78
  @uuid = amqp_headers[:message_id]
78
- headers = amqp_headers[:headers]
79
+ headers = amqp_headers[:headers].symbolize_keys
79
80
  @format_version = headers[:format_version].to_i
80
81
  @flags = headers[:flags].to_i
81
82
  @expires_at = headers[:expires_at].to_i
@@ -1,5 +1,4 @@
1
1
  require 'amqp'
2
- require 'mq'
3
2
 
4
3
  module Beetle
5
4
  # Manages subscriptions and message processing on the receiver side of things.
@@ -10,9 +9,10 @@ module Beetle
10
9
  super
11
10
  @servers.concat @client.additional_subscription_servers
12
11
  @handlers = {}
13
- @amqp_connections = {}
14
- @mqs = {}
12
+ @connections = {}
13
+ @channels = {}
15
14
  @subscriptions = {}
15
+ @listened_queues = []
16
16
  end
17
17
 
18
18
  # the client calls this method to subscribe to a list of queues.
@@ -24,11 +24,12 @@ module Beetle
24
24
  #
25
25
  # yields before entering the eventmachine loop (if a block was given)
26
26
  def listen_queues(queues) #:nodoc:
27
+ @listened_queues = queues
28
+ @exchanges_for_queues = exchanges_for_queues(queues)
27
29
  EM.run do
28
- exchanges = exchanges_for_queues(queues)
29
- create_exchanges(exchanges)
30
- bind_queues(queues)
31
- subscribe_queues(queues)
30
+ each_server do
31
+ connect_server connection_settings
32
+ end
32
33
  yield if block_given?
33
34
  end
34
35
  end
@@ -47,10 +48,10 @@ module Beetle
47
48
 
48
49
  # closes all AMQP connections and stops the eventmachine loop
49
50
  def stop! #:nodoc:
50
- if @amqp_connections.empty?
51
+ if @connections.empty?
51
52
  EM.stop_event_loop
52
53
  else
53
- server, connection = @amqp_connections.shift
54
+ server, connection = @connections.shift
54
55
  logger.debug "Beetle: closing connection to #{server}"
55
56
  connection.close { stop! }
56
57
  end
@@ -74,30 +75,19 @@ module Beetle
74
75
  end
75
76
 
76
77
  def create_exchanges(exchanges)
77
- each_server do
78
- exchanges.each { |name| exchange(name) }
79
- end
78
+ exchanges.each { |name| exchange(name) }
80
79
  end
81
80
 
82
81
  def bind_queues(queues)
83
- each_server do
84
- queues.each { |name| queue(name) }
85
- end
82
+ queues.each { |name| queue(name) }
86
83
  end
87
84
 
88
85
  def subscribe_queues(queues)
89
- each_server do
90
- queues.each { |name| subscribe(name) if @handlers.include?(name) }
91
- end
86
+ queues.each { |name| subscribe(name) if @handlers.include?(name) }
92
87
  end
93
88
 
94
- # returns the mq object for the given server or returns a new one created with the
95
- # prefetch(1) option. this tells it to just send one message to the receiving buffer
96
- # (instead of filling it). this is necesssary to ensure that one subscriber always just
97
- # handles one single message. we cannot ensure reliability if the buffer is filled with
98
- # messages and crashes.
99
- def mq(server=@server)
100
- @mqs[server] ||= MQ.new(amqp_connection).prefetch(1)
89
+ def channel(server=@server)
90
+ @channels[server]
101
91
  end
102
92
 
103
93
  def subscriptions(server=@server)
@@ -116,12 +106,8 @@ module Beetle
116
106
  callback = create_subscription_callback(queue_name, amqp_queue_name, handler, opts)
117
107
  keys = opts.slice(*SUBSCRIPTION_KEYS).merge(:key => "#", :ack => true)
118
108
  logger.debug "Beetle: subscribing to queue #{amqp_queue_name} with key # on server #{@server}"
119
- begin
120
- queues[queue_name].subscribe(keys, &callback)
121
- subscriptions[queue_name] = [keys, callback]
122
- rescue MQ::Error
123
- error("Beetle: binding multiple handlers for the same queue isn't possible.")
124
- end
109
+ queues[queue_name].subscribe(keys, &callback)
110
+ subscriptions[queue_name] = [keys, callback]
125
111
  end
126
112
 
127
113
  def pause(queue_name)
@@ -147,13 +133,14 @@ module Beetle
147
133
  if result.reject?
148
134
  sleep 1
149
135
  header.reject(:requeue => true)
150
- elsif reply_to = header.properties[:reply_to]
136
+ elsif reply_to = header.attributes[:reply_to]
137
+ # logger.info "Beetle: sending reply to queue #{reply_to}"
151
138
  # require 'ruby-debug'
152
139
  # Debugger.start
153
140
  # debugger
154
141
  status = result == Beetle::RC::OK ? "OK" : "FAILED"
155
- exchange = MQ::Exchange.new(mq(server), :direct, "", :key => reply_to)
156
- exchange.publish(m.handler_result.to_s, :headers => {:status => status})
142
+ exchange = AMQP::Exchange.new(channel(server), :direct, "")
143
+ exchange.publish(m.handler_result.to_s, :routing_key => reply_to, :persistent => false, :headers => {:status => status})
157
144
  end
158
145
  # logger.debug "Beetle: processed message"
159
146
  rescue Exception
@@ -168,27 +155,63 @@ module Beetle
168
155
  end
169
156
 
170
157
  def create_exchange!(name, opts)
171
- mq.__send__(opts[:type], name, opts.slice(*EXCHANGE_CREATION_KEYS))
158
+ channel.__send__(opts[:type], name, opts.slice(*EXCHANGE_CREATION_KEYS))
172
159
  end
173
160
 
174
161
  def bind_queue!(queue_name, creation_keys, exchange_name, binding_keys)
175
- queue = mq.queue(queue_name, creation_keys)
162
+ queue = channel.queue(queue_name, creation_keys)
176
163
  exchange = exchange(exchange_name)
177
164
  queue.bind(exchange, binding_keys)
178
165
  queue
179
166
  end
180
167
 
181
- def amqp_connection(server=@server)
182
- @amqp_connections[server] ||= new_amqp_connection
168
+ def connection_settings
169
+ {
170
+ :host => current_host, :port => current_port, :logging => false,
171
+ :user => Beetle.config.user, :pass => Beetle.config.password, :vhost => Beetle.config.vhost,
172
+ :on_tcp_connection_failure => on_tcp_connection_failure
173
+ }
174
+ end
175
+
176
+ def on_tcp_connection_failure
177
+ Proc.new do |settings|
178
+ logger.warn "Beetle: connection failed: #{server_from_settings(settings)}"
179
+ EM::Timer.new(10) { connect_server(settings) }
180
+ end
183
181
  end
184
182
 
185
- def new_amqp_connection
186
- # FIXME: wtf, how to test that reconnection feature....
187
- con = AMQP.connect(:host => current_host, :port => current_port, :logging => false,
188
- :user => Beetle.config.user, :pass => Beetle.config.password, :vhost => Beetle.config.vhost)
189
- con.instance_variable_set("@on_disconnect", proc{ con.__send__(:reconnect) })
190
- con
183
+ def on_tcp_connection_loss(connection, settings)
184
+ # reconnect in 10 seconds, without enforcement
185
+ logger.warn "Beetle: lost connection: #{server_from_settings(settings)}. reconnecting."
186
+ connection.reconnect(false, 10)
191
187
  end
192
188
 
189
+ def connect_server(settings)
190
+ server = server_from_settings settings
191
+ logger.info "Beetle: connecting to rabbit #{server}"
192
+ AMQP.connect(settings) do |connection|
193
+ connection.on_tcp_connection_loss(&method(:on_tcp_connection_loss))
194
+ @connections[server] = connection
195
+ open_channel_and_subscribe(connection, settings)
196
+ end
197
+ rescue EventMachine::ConnectionError => e
198
+ # something serious went wrong, for example DNS lookup failure
199
+ # in this case, the on_tcp_connection_failure callback is never called automatically
200
+ logger.error "Beetle: connection failed: #{e.class}(#{e})"
201
+ settings[:on_tcp_connection_failure].call(settings)
202
+ end
203
+
204
+ def open_channel_and_subscribe(connection, settings)
205
+ server = server_from_settings settings
206
+ AMQP::Channel.new(connection) do |channel|
207
+ channel.auto_recovery = true
208
+ channel.prefetch(1)
209
+ set_current_server server
210
+ @channels[server] = channel
211
+ create_exchanges(@exchanges_for_queues)
212
+ bind_queues(@listened_queues)
213
+ subscribe_queues(@listened_queues)
214
+ end
215
+ end
193
216
  end
194
217
  end
data/script/console~ ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.expand_path("../lib",__FILE__))
@@ -0,0 +1,30 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+ require 'eventmachine'
3
+ require 'amqp'
4
+
5
+ class AMQPGemBehaviorTest < Test::Unit::TestCase
6
+ test "subscribing twice to the same queue raises a RuntimeError which throws us out of the event loop" do
7
+ begin
8
+ @exception = nil
9
+ EM.run do
10
+ AMQP.start do |connection|
11
+ begin
12
+ EM::Timer.new(1){ connection.close { EM.stop }}
13
+ channel = AMQP::Channel.new(connection)
14
+ channel.on_error { puts "woot"}
15
+ exchange = channel.topic("beetle_tests")
16
+ queue = AMQP::Queue.new(channel)
17
+ queue.bind(exchange, :key => "#")
18
+ queue.subscribe { }
19
+ queue.subscribe { }
20
+ rescue
21
+ # we never get here, because the subscription is deferred
22
+ # the only known way to avoid this is to use the block version of AMQP::Queue.new
23
+ end
24
+ end
25
+ end
26
+ rescue Exception => @exception
27
+ end
28
+ assert @exception
29
+ end
30
+ end
@@ -48,5 +48,9 @@ module Beetle
48
48
  @bs.send(:set_current_server, "xxx:123")
49
49
  assert_equal "xxx:123", @bs.server
50
50
  end
51
+
52
+ test "server_from_settings should create a valid server string from an AMQP settings hash" do
53
+ assert_equal "goofy:123", @bs.send(:server_from_settings, {:host => "goofy", :port => 123})
54
+ end
51
55
  end
52
56
  end
@@ -8,40 +8,18 @@ module Beetle
8
8
  end
9
9
 
10
10
  test "initially there should be no amqp connections" do
11
- assert_equal({}, @sub.instance_variable_get("@amqp_connections"))
11
+ assert_equal({}, @sub.instance_variable_get("@connections"))
12
12
  end
13
13
 
14
- test "initially there should be no instances of MQ" do
15
- assert_equal({}, @sub.instance_variable_get("@mqs"))
14
+ test "initially there should be no channels" do
15
+ assert_equal({}, @sub.instance_variable_get("@channels"))
16
16
  end
17
17
 
18
- test "acccessing an amq_connection for a server which doesn't have one should create it and associate it with the server" do
19
- @sub.expects(:new_amqp_connection).returns(42)
20
- # TODO: smarter way to test? what triggers the amqp_connection private method call?
21
- assert_equal 42, @sub.send(:amqp_connection)
22
- connections = @sub.instance_variable_get("@amqp_connections")
23
- assert_equal 42, connections[@sub.server]
24
- end
25
-
26
- test "new amqp connections should be created using current host and port" do
27
- m = mock("dummy")
28
- expected_amqp_options = {
29
- :host => @sub.send(:current_host), :port => @sub.send(:current_port),
30
- :user => "guest", :pass => "guest", :vhost => "/", :logging => false
31
- }
32
- AMQP.expects(:connect).with(expected_amqp_options).returns(m)
33
- # TODO: smarter way to test? what triggers the amqp_connection private method call?
34
- assert_equal m, @sub.send(:new_amqp_connection)
35
- end
36
-
37
- test "mq instances should be created for the current server if accessed" do
38
- @sub.expects(:amqp_connection).returns(11)
39
- mq_mock = mock('mq')
40
- mq_mock.expects(:prefetch).with(1).returns(42)
41
- MQ.expects(:new).with(11).returns(mq_mock)
42
- assert_equal 42, @sub.send(:mq)
43
- mqs = @sub.instance_variable_get("@mqs")
44
- assert_equal 42, mqs[@sub.server]
18
+ test "channel should return the channel associated with the current server, if there is one" do
19
+ channel = mock("donald")
20
+ @sub.instance_variable_set("@channels", {"donald:1" => channel})
21
+ assert_nil @sub.send(:channel, "goofy:123")
22
+ assert_equal channel, @sub.send(:channel, "donald:1")
45
23
  end
46
24
 
47
25
  test "stop! should close all amqp connections and then stop the event loop" do
@@ -49,7 +27,7 @@ module Beetle
49
27
  connection1.expects(:close).yields
50
28
  connection2 = mock('con2')
51
29
  connection2.expects(:close).yields
52
- @sub.instance_variable_set "@amqp_connections", [["server1", connection1], ["server2",connection2]]
30
+ @sub.instance_variable_set "@connections", [["server1", connection1], ["server2",connection2]]
53
31
  EM.expects(:stop_event_loop)
54
32
  @sub.send(:stop!)
55
33
  end
@@ -156,31 +134,43 @@ module Beetle
156
134
  q.expects(:bind).with(:the_exchange, {:key => "haha.#"})
157
135
  m = mock("MQ")
158
136
  m.expects(:queue).with("some_queue", :durable => true, :passive => false, :auto_delete => false, :exclusive => false).returns(q)
159
- @sub.expects(:mq).returns(m)
137
+ @sub.expects(:channel).returns(m)
160
138
 
161
139
  @sub.send(:queue, "some_queue")
162
140
  assert_equal q, @sub.send(:queues)["some_queue"]
163
141
  end
164
142
 
165
- test "binding queues should iterate over all servers" do
166
- s = sequence("binding")
143
+ test "binding queues should bind all queues" do
167
144
  @client.register_queue(:x)
168
145
  @client.register_queue(:y)
169
146
  @client.register_handler(%w(x y)){}
170
- @sub.servers = %w(a b)
171
- @sub.expects(:set_current_server).with("a").in_sequence(s)
172
- @sub.expects(:queue).with("x").in_sequence(s)
173
- @sub.expects(:queue).with("y").in_sequence(s)
174
- @sub.expects(:set_current_server).with("b").in_sequence(s)
175
- @sub.expects(:queue).with("x").in_sequence(s)
176
- @sub.expects(:queue).with("y").in_sequence(s)
147
+ @sub.expects(:queue).with("x")
148
+ @sub.expects(:queue).with("y")
177
149
  @sub.send(:bind_queues, %W(x y))
178
150
  end
179
151
 
152
+ test "subscribing to queues should subscribe on all queues" do
153
+ @client.register_queue(:x)
154
+ @client.register_queue(:y)
155
+ @client.register_handler(%w(x y)){}
156
+ @sub.expects(:subscribe).with("x")
157
+ @sub.expects(:subscribe).with("y")
158
+ @sub.send(:subscribe_queues, %W(x y))
159
+ end
160
+
180
161
  test "should not try to bind a queue for an exchange which has no queue" do
181
162
  @client.register_message(:without_queue)
182
163
  assert_equal [], @sub.send(:queues_for_exchanges, ["without_queue"])
183
164
  end
165
+
166
+ test "should not subscribe on a queue for which there is no handler" do
167
+ @client.register_queue(:x)
168
+ @client.register_queue(:y)
169
+ @client.register_handler(%w(y)){}
170
+ @sub.expects(:subscribe).with("y")
171
+ @sub.send(:subscribe_queues, %W(x y))
172
+ end
173
+
184
174
  end
185
175
 
186
176
  class SubscriberExchangeManagementTest < Test::Unit::TestCase
@@ -197,26 +187,20 @@ module Beetle
197
187
  @client.register_exchange("some_exchange", "type" => "topic", "durable" => true)
198
188
  m = mock("AMQP")
199
189
  m.expects(:topic).with("some_exchange", :durable => true).returns(42)
200
- @sub.expects(:mq).returns(m)
190
+ @sub.expects(:channel).returns(m)
201
191
  ex = @sub.send(:exchange, "some_exchange")
202
192
  assert @sub.send(:exchanges).include?("some_exchange")
203
193
  ex2 = @sub.send(:exchange, "some_exchange")
204
194
  assert_equal ex2, ex
205
195
  end
206
196
 
207
- test "should create exchanges for all exchanges passed to create_exchanges, for all servers" do
208
- @sub.servers = %w(x y)
197
+ test "should create exchanges for all exchanges passed to create_exchanges for the current server" do
209
198
  @client.register_queue(:donald, :exchange => 'duck')
210
199
  @client.register_queue(:mickey)
211
200
  @client.register_queue(:mouse, :exchange => 'mickey')
212
201
 
213
- exchange_creation = sequence("exchange creation")
214
- @sub.expects(:set_current_server).with('x').in_sequence(exchange_creation)
215
- @sub.expects(:create_exchange!).with("duck", anything).in_sequence(exchange_creation)
216
- @sub.expects(:create_exchange!).with("mickey", anything).in_sequence(exchange_creation)
217
- @sub.expects(:set_current_server).with('y', anything).in_sequence(exchange_creation)
218
- @sub.expects(:create_exchange!).with("duck", anything).in_sequence(exchange_creation)
219
- @sub.expects(:create_exchange!).with("mickey", anything).in_sequence(exchange_creation)
202
+ @sub.expects(:create_exchange!).with("duck", anything)
203
+ @sub.expects(:create_exchange!).with("mickey", anything)
220
204
  @sub.send(:create_exchanges, %w(duck mickey))
221
205
  end
222
206
  end
@@ -240,7 +224,7 @@ module Beetle
240
224
  assert_nothing_raised { @callback.call(header, 'foo') }
241
225
  end
242
226
 
243
- test "should call reject on the message header when processing the handler returns true on recover?" do
227
+ test "should call reject on the message header when processing the handler returns true on reject?" do
244
228
  header = header_with_params({})
245
229
  result = mock("result")
246
230
  result.expects(:reject?).returns(true)
@@ -256,10 +240,10 @@ module Beetle
256
240
  Message.any_instance.expects(:process).returns(result)
257
241
  Message.any_instance.expects(:handler_result).returns("response-data")
258
242
  mq = mock("MQ")
259
- @sub.expects(:mq).with(@sub.server).returns(mq)
243
+ @sub.expects(:channel).with(@sub.server).returns(mq)
260
244
  exchange = mock("exchange")
261
- exchange.expects(:publish).with("response-data", :headers => {:status => "OK"})
262
- MQ::Exchange.expects(:new).with(mq, :direct, "", :key => "tmp-queue").returns(exchange)
245
+ exchange.expects(:publish).with("response-data", :routing_key => "tmp-queue", :headers => {:status => "OK"}, :persistent => false)
246
+ AMQP::Exchange.expects(:new).with(mq, :direct, "").returns(exchange)
263
247
  @callback.call(header, 'foo')
264
248
  end
265
249
 
@@ -269,10 +253,10 @@ module Beetle
269
253
  Message.any_instance.expects(:process).returns(result)
270
254
  Message.any_instance.expects(:handler_result).returns(nil)
271
255
  mq = mock("MQ")
272
- @sub.expects(:mq).with(@sub.server).returns(mq)
256
+ @sub.expects(:channel).with(@sub.server).returns(mq)
273
257
  exchange = mock("exchange")
274
- exchange.expects(:publish).with("", :headers => {:status => "FAILED"})
275
- MQ::Exchange.expects(:new).with(mq, :direct, "", :key => "tmp-queue").returns(exchange)
258
+ exchange.expects(:publish).with("", :routing_key => "tmp-queue", :headers => {:status => "FAILED"}, :persistent => false)
259
+ AMQP::Exchange.expects(:new).with(mq, :direct, "").returns(exchange)
276
260
  @callback.call(header, 'foo')
277
261
  end
278
262
 
@@ -284,18 +268,6 @@ module Beetle
284
268
  @sub = @client.send(:subscriber)
285
269
  end
286
270
 
287
- test "subscribe should create subscriptions on all queues for all servers" do
288
- @sub.servers << "localhost:7777"
289
- @client.register_message(:a)
290
- @client.register_message(:b)
291
- @client.register_queue(:a)
292
- @client.register_queue(:b)
293
- @client.register_handler(%W(a b)){}
294
- @sub.expects(:subscribe).with("a").times(2)
295
- @sub.expects(:subscribe).with("b").times(2)
296
- @sub.send(:subscribe_queues, %W(a b))
297
- end
298
-
299
271
  test "subscribe should subscribe with a subscription callback created from the registered block and remember the subscription" do
300
272
  @client.register_queue(:some_queue, :exchange => "some_exchange", :key => "some_key")
301
273
  server = @sub.server
@@ -312,28 +284,32 @@ module Beetle
312
284
  q = mock("QUEUE")
313
285
  subscription_options = {:ack => true, :key => "#"}
314
286
  q.expects(:subscribe).with(subscription_options).yields(header, "foo")
315
- @sub.expects(:queues).returns({"some_queue" => q}).twice
287
+ @sub.expects(:queues).returns({"some_queue" => q}).once
316
288
  @sub.send(:subscribe, "some_queue")
317
289
  assert block_called
318
290
  assert @sub.__send__(:has_subscription?, "some_queue")
319
- q.expects(:subscribe).with(subscription_options).raises(MQ::Error)
320
- assert_raises(Error) { @sub.send(:subscribe, "some_queue") }
291
+ # q.expects(:subscribe).with(subscription_options).raises(MQ::Error)
292
+ # assert_raises(Error) { @sub.send(:subscribe, "some_queue") }
321
293
  end
322
294
 
323
295
  test "subscribe should fail if no handler exists for given message" do
324
296
  assert_raises(Error){ @sub.send(:subscribe, "some_queue") }
325
297
  end
326
298
 
327
- test "listening on queues should use eventmachine. create exchanges. bind queues. install subscribers. and yield." do
299
+ test "listening on queues should use eventmachine, connect to each server, and yield" do
328
300
  @client.register_exchange(:an_exchange)
329
301
  @client.register_queue(:a_queue, :exchange => :an_exchange)
330
302
  @client.register_message(:a_message, :key => "foo", :exchange => :an_exchange)
303
+ @sub.servers << "localhost:7777"
331
304
 
305
+ @sub.expects(:connect_server).twice
332
306
  EM.expects(:run).yields
333
- @sub.expects(:create_exchanges).with(["an_exchange"])
334
- @sub.expects(:bind_queues).with(["a_queue"])
335
- @sub.expects(:subscribe_queues).with(["a_queue"])
336
- @sub.listen_queues(["a_queue"]) {}
307
+ # @sub.expects(:create_exchanges).with(["an_exchange"])
308
+ # @sub.expects(:bind_queues).with(["a_queue"])
309
+ # @sub.expects(:subscribe_queues).with(["a_queue"])
310
+ yielded = false
311
+ @sub.listen_queues(["a_queue"]) { yielded = true}
312
+ assert yielded
337
313
  end
338
314
  end
339
315
 
@@ -357,4 +333,64 @@ module Beetle
357
333
 
358
334
  end
359
335
 
336
+ class ConnectionTest < Test::Unit::TestCase
337
+ def setup
338
+ @client = Client.new
339
+ @sub = @client.send(:subscriber)
340
+ @sub.send(:set_current_server, "mickey:42")
341
+ @settings = @sub.send(:connection_settings)
342
+ end
343
+
344
+ test "connection settings should use current host and port and specify connection failure callback" do
345
+ assert_equal "mickey", @settings[:host]
346
+ assert_equal 42, @settings[:port]
347
+ assert @settings.has_key?(:on_tcp_connection_failure)
348
+ end
349
+
350
+ test "tcp connection failure should try to connect again after 10 seconds" do
351
+ cb = @sub.send(:on_tcp_connection_failure)
352
+ EM::Timer.expects(:new).with(10).yields
353
+ @sub.expects(:connect_server).with(@settings)
354
+ @sub.logger.expects(:warn).with("Beetle: connection failed: mickey:42")
355
+ cb.call(@settings)
356
+ end
357
+
358
+ test "tcp connection loss handler tries to reconnect" do
359
+ connection = mock("connection")
360
+ connection.expects(:reconnect).with(false, 10)
361
+ @sub.logger.expects(:warn).with("Beetle: lost connection: mickey:42. reconnecting.")
362
+ @sub.send(:on_tcp_connection_loss, connection, {:host => "mickey", :port => 42})
363
+ end
364
+
365
+ test "event machine connection error" do
366
+ connection = mock("connection")
367
+ AMQP.expects(:connect).raises(EventMachine::ConnectionError)
368
+ @settings[:on_tcp_connection_failure].expects(:call).with(@settings)
369
+ @sub.send(:connect_server, @settings)
370
+ end
371
+
372
+ test "successfull connection to broker" do
373
+ connection = mock("connection")
374
+ connection.expects(:on_tcp_connection_loss)
375
+ @sub.expects(:open_channel_and_subscribe).with(connection, @settings)
376
+ AMQP.expects(:connect).with(@settings).yields(connection)
377
+ @sub.send(:connect_server, @settings)
378
+ assert_equal connection, @sub.instance_variable_get("@connections")["mickey:42"]
379
+ end
380
+
381
+ test "channel opening, exchange creation, queue bindings and subscription" do
382
+ connection = mock("connection")
383
+ channel = mock("channel")
384
+ channel.expects(:prefetch).with(1)
385
+ channel.expects(:auto_recovery=).with(true)
386
+ AMQP::Channel.expects(:new).with(connection).yields(channel)
387
+ @sub.expects(:create_exchanges)
388
+ @sub.expects(:bind_queues)
389
+ @sub.expects(:subscribe_queues)
390
+ @sub.send(:open_channel_and_subscribe, connection, @settings)
391
+ assert_equal channel, @sub.instance_variable_get("@channels")["mickey:42"]
392
+ end
393
+
394
+ end
395
+
360
396
  end
@@ -0,0 +1,25 @@
1
+ # colorized output for Test::Unit / MiniTest
2
+ begin
3
+ if RUBY_VERSION < "1.9"
4
+ require 'redgreen'
5
+ else
6
+ module ColorizedDots
7
+ require 'ansi/code'
8
+ def run(runner)
9
+ r = super
10
+ ANSI.ansi(r, ANSI_COLOR_MAPPING[r])
11
+ end
12
+ ANSI_COLOR_MAPPING = Hash.new(:white).merge!('.' => :green, 'S' => :magenta, 'F' => :yellow, 'E' => :red )
13
+ end
14
+ class MiniTest::Unit
15
+ TestCase.send(:include, ColorizedDots)
16
+ def status(io = @@out)
17
+ format = "%d tests, %d assertions, %d failures, %d errors, %d skips"
18
+ color = (errors + failures) > 0 ? :red : :green
19
+ io.puts ANSI.ansi(format % [test_count, assertion_count, failures, errors, skips], color)
20
+ end
21
+ end
22
+ end
23
+ rescue LoadError => e
24
+ # do nothing
25
+ end unless ENV['TM_FILENAME']
data/test/test_helper.rb CHANGED
@@ -4,11 +4,7 @@ require 'mocha'
4
4
  require 'active_support/testing/declarative'
5
5
 
6
6
  require File.expand_path(File.dirname(__FILE__) + '/../lib/beetle')
7
-
8
- begin
9
- require 'redgreen' unless ENV['TM_FILENAME']
10
- rescue LoadError => e
11
- end
7
+ require File.expand_path(File.dirname(__FILE__) + '/colorized_test_output')
12
8
 
13
9
  # we can remove this hack which is needed only for testing
14
10
  begin
@@ -29,13 +25,15 @@ end
29
25
  Beetle.config.logger = Logger.new(File.dirname(__FILE__) + '/../test.log')
30
26
  Beetle.config.redis_server = "localhost:6379"
31
27
 
28
+
32
29
  def header_with_params(opts = {})
33
30
  beetle_headers = Beetle::Message.publishing_options(opts)
34
31
  header = mock("header")
35
- header.stubs(:properties).returns(beetle_headers)
32
+ header.stubs(:attributes).returns(beetle_headers)
36
33
  header
37
34
  end
38
35
 
36
+
39
37
  def redis_stub(name, opts = {})
40
38
  default_port = opts['port'] || "1234"
41
39
  default_host = opts['host'] || "foo"
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: beetle
3
3
  version: !ruby/object:Gem::Version
4
- hash: 15424035
4
+ hash: 15424045
5
5
  prerelease: 6
6
6
  segments:
7
7
  - 0
8
8
  - 3
9
9
  - 0
10
10
  - rc
11
- - 3
12
- version: 0.3.0.rc.3
11
+ - 4
12
+ version: 0.3.0.rc.4
13
13
  platform: ruby
14
14
  authors:
15
15
  - Stefan Kaes
@@ -20,7 +20,8 @@ autorequire:
20
20
  bindir: bin
21
21
  cert_chain: []
22
22
 
23
- date: 2011-10-05 00:00:00 Z
23
+ date: 2011-10-11 00:00:00 +02:00
24
+ default_executable: beetle
24
25
  dependencies:
25
26
  - !ruby/object:Gem::Dependency
26
27
  name: uuid4r
@@ -44,14 +45,14 @@ dependencies:
44
45
  requirement: &id002 !ruby/object:Gem::Requirement
45
46
  none: false
46
47
  requirements:
47
- - - ~>
48
+ - - "="
48
49
  - !ruby/object:Gem::Version
49
- hash: 1
50
+ hash: 19
50
51
  segments:
51
52
  - 0
52
53
  - 7
53
- - 1
54
- version: 0.7.1
54
+ - 8
55
+ version: 0.7.8
55
56
  type: :runtime
56
57
  version_requirements: *id002
57
58
  - !ruby/object:Gem::Dependency
@@ -87,25 +88,57 @@ dependencies:
87
88
  type: :runtime
88
89
  version_requirements: *id004
89
90
  - !ruby/object:Gem::Dependency
90
- name: amqp
91
+ name: amq-client
91
92
  prerelease: false
92
93
  requirement: &id005 !ruby/object:Gem::Requirement
93
94
  none: false
94
95
  requirements:
95
- - - ~>
96
+ - - "="
96
97
  - !ruby/object:Gem::Version
97
- hash: 9
98
+ hash: 57
98
99
  segments:
99
100
  - 0
100
- - 6
101
- - 7
102
- version: 0.6.7
101
+ - 8
102
+ - 3
103
+ version: 0.8.3
103
104
  type: :runtime
104
105
  version_requirements: *id005
105
106
  - !ruby/object:Gem::Dependency
106
- name: activesupport
107
+ name: amq-protocol
107
108
  prerelease: false
108
109
  requirement: &id006 !ruby/object:Gem::Requirement
110
+ none: false
111
+ requirements:
112
+ - - "="
113
+ - !ruby/object:Gem::Version
114
+ hash: 61
115
+ segments:
116
+ - 0
117
+ - 8
118
+ - 1
119
+ version: 0.8.1
120
+ type: :runtime
121
+ version_requirements: *id006
122
+ - !ruby/object:Gem::Dependency
123
+ name: amqp
124
+ prerelease: false
125
+ requirement: &id007 !ruby/object:Gem::Requirement
126
+ none: false
127
+ requirements:
128
+ - - "="
129
+ - !ruby/object:Gem::Version
130
+ hash: 63
131
+ segments:
132
+ - 0
133
+ - 8
134
+ - 0
135
+ version: 0.8.0
136
+ type: :runtime
137
+ version_requirements: *id007
138
+ - !ruby/object:Gem::Dependency
139
+ name: activesupport
140
+ prerelease: false
141
+ requirement: &id008 !ruby/object:Gem::Requirement
109
142
  none: false
110
143
  requirements:
111
144
  - - ">="
@@ -117,11 +150,11 @@ dependencies:
117
150
  - 4
118
151
  version: 2.3.4
119
152
  type: :runtime
120
- version_requirements: *id006
153
+ version_requirements: *id008
121
154
  - !ruby/object:Gem::Dependency
122
155
  name: daemons
123
156
  prerelease: false
124
- requirement: &id007 !ruby/object:Gem::Requirement
157
+ requirement: &id009 !ruby/object:Gem::Requirement
125
158
  none: false
126
159
  requirements:
127
160
  - - ">="
@@ -133,11 +166,11 @@ dependencies:
133
166
  - 10
134
167
  version: 1.0.10
135
168
  type: :runtime
136
- version_requirements: *id007
169
+ version_requirements: *id009
137
170
  - !ruby/object:Gem::Dependency
138
171
  name: rake
139
172
  prerelease: false
140
- requirement: &id008 !ruby/object:Gem::Requirement
173
+ requirement: &id010 !ruby/object:Gem::Requirement
141
174
  none: false
142
175
  requirements:
143
176
  - - ">="
@@ -149,11 +182,11 @@ dependencies:
149
182
  - 7
150
183
  version: 0.8.7
151
184
  type: :development
152
- version_requirements: *id008
185
+ version_requirements: *id010
153
186
  - !ruby/object:Gem::Dependency
154
187
  name: mocha
155
188
  prerelease: false
156
- requirement: &id009 !ruby/object:Gem::Requirement
189
+ requirement: &id011 !ruby/object:Gem::Requirement
157
190
  none: false
158
191
  requirements:
159
192
  - - ">="
@@ -163,11 +196,11 @@ dependencies:
163
196
  - 0
164
197
  version: "0"
165
198
  type: :development
166
- version_requirements: *id009
199
+ version_requirements: *id011
167
200
  - !ruby/object:Gem::Dependency
168
201
  name: rcov
169
202
  prerelease: false
170
- requirement: &id010 !ruby/object:Gem::Requirement
203
+ requirement: &id012 !ruby/object:Gem::Requirement
171
204
  none: false
172
205
  requirements:
173
206
  - - ">="
@@ -177,11 +210,11 @@ dependencies:
177
210
  - 0
178
211
  version: "0"
179
212
  type: :development
180
- version_requirements: *id010
213
+ version_requirements: *id012
181
214
  - !ruby/object:Gem::Dependency
182
215
  name: redgreen
183
216
  prerelease: false
184
- requirement: &id011 !ruby/object:Gem::Requirement
217
+ requirement: &id013 !ruby/object:Gem::Requirement
185
218
  none: false
186
219
  requirements:
187
220
  - - ">="
@@ -191,11 +224,11 @@ dependencies:
191
224
  - 0
192
225
  version: "0"
193
226
  type: :development
194
- version_requirements: *id011
227
+ version_requirements: *id013
195
228
  - !ruby/object:Gem::Dependency
196
229
  name: wirble
197
230
  prerelease: false
198
- requirement: &id012 !ruby/object:Gem::Requirement
231
+ requirement: &id014 !ruby/object:Gem::Requirement
199
232
  none: false
200
233
  requirements:
201
234
  - - ">="
@@ -205,11 +238,11 @@ dependencies:
205
238
  - 0
206
239
  version: "0"
207
240
  type: :development
208
- version_requirements: *id012
241
+ version_requirements: *id014
209
242
  - !ruby/object:Gem::Dependency
210
243
  name: cucumber
211
244
  prerelease: false
212
- requirement: &id013 !ruby/object:Gem::Requirement
245
+ requirement: &id015 !ruby/object:Gem::Requirement
213
246
  none: false
214
247
  requirements:
215
248
  - - ">="
@@ -221,11 +254,11 @@ dependencies:
221
254
  - 2
222
255
  version: 0.7.2
223
256
  type: :development
224
- version_requirements: *id013
257
+ version_requirements: *id015
225
258
  - !ruby/object:Gem::Dependency
226
259
  name: daemon_controller
227
260
  prerelease: false
228
- requirement: &id014 !ruby/object:Gem::Requirement
261
+ requirement: &id016 !ruby/object:Gem::Requirement
229
262
  none: false
230
263
  requirements:
231
264
  - - ">="
@@ -235,7 +268,7 @@ dependencies:
235
268
  - 0
236
269
  version: "0"
237
270
  type: :development
238
- version_requirements: *id014
271
+ version_requirements: *id016
239
272
  description: A highly available, reliable messaging infrastructure
240
273
  email: developers@xing.com
241
274
  executables:
@@ -255,6 +288,7 @@ files:
255
288
  - examples/handling_exceptions.rb
256
289
  - examples/multiple_exchanges.rb
257
290
  - examples/multiple_queues.rb
291
+ - examples/nonexistent_server.rb
258
292
  - examples/pause_and_resume.rb
259
293
  - examples/redundant.rb
260
294
  - examples/rpc.rb
@@ -289,6 +323,7 @@ files:
289
323
  - features/support/test_daemons/redis_configuration_client.rb
290
324
  - features/support/test_daemons/redis_configuration_server.rb
291
325
  - script/console
326
+ - script/console~
292
327
  - script/start_rabbit
293
328
  - beetle.gemspec
294
329
  - Rakefile
@@ -297,6 +332,7 @@ files:
297
332
  - REDIS_AUTO_FAILOVER.rdoc
298
333
  - RELEASE_NOTES.rdoc
299
334
  - MIT-LICENSE
335
+ - test/beetle/amqp_gem_behavior_test.rb
300
336
  - test/beetle/base_test.rb
301
337
  - test/beetle/client_test.rb
302
338
  - test/beetle/configuration_test.rb
@@ -311,8 +347,10 @@ files:
311
347
  - test/beetle/redis_master_file_test.rb
312
348
  - test/beetle/subscriber_test.rb
313
349
  - test/beetle_test.rb
350
+ - test/colorized_test_output.rb
314
351
  - test/test_helper.rb
315
352
  - bin/beetle
353
+ has_rdoc: true
316
354
  homepage: http://xing.github.com/beetle/
317
355
  licenses: []
318
356
 
@@ -344,11 +382,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
344
382
  requirements: []
345
383
 
346
384
  rubyforge_project:
347
- rubygems_version: 1.8.11
385
+ rubygems_version: 1.6.2
348
386
  signing_key:
349
387
  specification_version: 3
350
388
  summary: High Availability AMQP Messaging with Redundant Queues
351
389
  test_files:
390
+ - test/beetle/amqp_gem_behavior_test.rb
352
391
  - test/beetle/base_test.rb
353
392
  - test/beetle/client_test.rb
354
393
  - test/beetle/configuration_test.rb
@@ -363,4 +402,5 @@ test_files:
363
402
  - test/beetle/redis_master_file_test.rb
364
403
  - test/beetle/subscriber_test.rb
365
404
  - test/beetle_test.rb
405
+ - test/colorized_test_output.rb
366
406
  - test/test_helper.rb