beetle 0.2.13 → 0.3.0.rc.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +2 -0
- data/beetle.gemspec +2 -3
- data/examples/attempts.rb +2 -2
- data/examples/pause_and_resume.rb +75 -0
- data/examples/simple.rb +3 -3
- data/examples/test_publisher.rb +32 -0
- data/lib/beetle/client.rb +42 -12
- data/lib/beetle/handler.rb +22 -6
- data/lib/beetle/publisher.rb +8 -4
- data/lib/beetle/subscriber.rb +41 -6
- data/lib/beetle.rb +1 -1
- data/test/beetle/client_test.rb +35 -3
- data/test/beetle/handler_test.rb +10 -3
- data/test/beetle/publisher_test.rb +1 -1
- data/test/beetle/subscriber_test.rb +80 -5
- metadata +37 -50
- data/script/console~ +0 -2
data/Rakefile
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'rake'
|
2
2
|
require 'rake/testtask'
|
3
3
|
require 'rcov/rcovtask'
|
4
|
+
# rake 0.9.2 hack to supress deprecation warnings caused by cucumber
|
5
|
+
include Rake::DSL if RAKEVERSION >= "0.9"
|
4
6
|
require 'cucumber/rake/task'
|
5
7
|
|
6
8
|
# 1.8/1.9 compatible way of loading lib/beetle.rb
|
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
|
+
s.version = "0.3.0.rc.1"
|
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,8 +29,7 @@ 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", ["
|
33
|
-
s.add_runtime_dependency("bunny-ext", [">= 0.6.5"])
|
32
|
+
s.add_runtime_dependency("bunny", ["~> 0.7.1"])
|
34
33
|
s.add_runtime_dependency("redis", ["= 2.0.4"])
|
35
34
|
s.add_runtime_dependency("amqp", ["~> 0.6.7"])
|
36
35
|
s.add_runtime_dependency("activesupport", [">= 2.3.4"])
|
data/examples/attempts.rb
CHANGED
@@ -47,7 +47,7 @@ class Handler < Beetle::Handler
|
|
47
47
|
# we're stopping the event loop so this script stops after that
|
48
48
|
def failure(result)
|
49
49
|
super
|
50
|
-
$client.stop_listening
|
50
|
+
EM.add_timer(1){$client.stop_listening}
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
@@ -62,5 +62,5 @@ $client.listen
|
|
62
62
|
|
63
63
|
# error handling, if everything went right this shouldn't happen.
|
64
64
|
if $exceptions != $max_exceptions + 1
|
65
|
-
raise "
|
65
|
+
raise "Something is fishy. Failed #{$exceptions} times"
|
66
66
|
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# attempts.rb
|
2
|
+
# this example shows you how to use the exception limiting feature of beetle
|
3
|
+
# it allows you to control the number of retries your handler will go through
|
4
|
+
# with one message before giving up on it
|
5
|
+
#
|
6
|
+
# ! check the examples/README.rdoc for information on starting your redis/rabbit !
|
7
|
+
#
|
8
|
+
# start it with ruby attempts.rb
|
9
|
+
|
10
|
+
require "rubygems"
|
11
|
+
require File.expand_path("../lib/beetle", File.dirname(__FILE__))
|
12
|
+
|
13
|
+
# set Beetle log level to info, less noisy than debug
|
14
|
+
Beetle.config.logger.level = Logger::INFO
|
15
|
+
|
16
|
+
# setup client
|
17
|
+
$client = Beetle::Client.new
|
18
|
+
$client.register_queue(:test)
|
19
|
+
$client.register_message(:test)
|
20
|
+
|
21
|
+
# purge the test queue
|
22
|
+
$client.purge(:test)
|
23
|
+
|
24
|
+
# initially, our service is online
|
25
|
+
$online = true
|
26
|
+
|
27
|
+
# declare a handler class for message processing
|
28
|
+
# in this example we've not only overwritten the process method but also the
|
29
|
+
# error and failure methods of the handler baseclass
|
30
|
+
class Handler < Beetle::Handler
|
31
|
+
|
32
|
+
# called when the handler receives the message - fail everytime
|
33
|
+
def process
|
34
|
+
puts "received message #{message.data} online=#{$online}"
|
35
|
+
unless $online
|
36
|
+
$client.pause_listening(:test)
|
37
|
+
raise "offline"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# called when handler process raised an exception
|
42
|
+
def error(exception)
|
43
|
+
puts "execution failed: #{exception}"
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
# publish a decent amount of messages
|
49
|
+
# 1000.times do |i|
|
50
|
+
# $client.publish(:test, i)
|
51
|
+
# end
|
52
|
+
# $client.stop_publishing
|
53
|
+
|
54
|
+
# register our handler to the message, configure it to our max_exceptions limit, we configure a delay of 0 to have it not wait before retrying
|
55
|
+
$client.register_handler(:test, Handler, :exceptions => 1, :delay => 0)
|
56
|
+
|
57
|
+
# and start our listening loop...
|
58
|
+
$client.listen do
|
59
|
+
n = 0
|
60
|
+
ptimer = EM.add_periodic_timer(0.1) do
|
61
|
+
data = (n+=1)
|
62
|
+
puts "publishing message #{data}"
|
63
|
+
$client.publish(:test, data)
|
64
|
+
end
|
65
|
+
|
66
|
+
EM.add_periodic_timer(2) do
|
67
|
+
$online = !$online
|
68
|
+
$client.resume_listening(:test) if $online
|
69
|
+
end
|
70
|
+
|
71
|
+
EM.add_timer(10) do
|
72
|
+
$client.pause_listening
|
73
|
+
EM.add_timer(1) { $client.stop_listening }
|
74
|
+
end
|
75
|
+
end
|
data/examples/simple.rb
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
#
|
6
6
|
# ! check the examples/README.rdoc for information on starting your redis/rabbit !
|
7
7
|
#
|
8
|
-
# start it with ruby
|
8
|
+
# start it with ruby simple.rb
|
9
9
|
|
10
10
|
require "rubygems"
|
11
11
|
require File.expand_path("../lib/beetle", File.dirname(__FILE__))
|
@@ -27,8 +27,8 @@ client.deduplication_store.flushdb
|
|
27
27
|
# register our handler to the message, check out the message.rb for more stuff you can get from the message object
|
28
28
|
client.register_handler(:test) {|message| puts "got message: #{message.data}"}
|
29
29
|
|
30
|
-
# publish our message
|
31
|
-
client.publish(:test, 'bam')
|
30
|
+
# publish our message (NOTE: empty message bodies don't work, most likely due to bugs in bunny/amqp)
|
31
|
+
puts client.publish(:test, 'bam')
|
32
32
|
|
33
33
|
# start listening
|
34
34
|
# this starts the event machine event loop using EM.run
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# attempts.rb
|
2
|
+
# this example shows you how to use the exception limiting feature of beetle
|
3
|
+
# it allows you to control the number of retries your handler will go through
|
4
|
+
# with one message before giving up on it
|
5
|
+
#
|
6
|
+
# ! check the examples/README.rdoc for information on starting your redis/rabbit !
|
7
|
+
#
|
8
|
+
# start it with ruby attempts.rb
|
9
|
+
|
10
|
+
require "rubygems"
|
11
|
+
require File.expand_path("../lib/beetle", File.dirname(__FILE__))
|
12
|
+
require "eventmachine"
|
13
|
+
|
14
|
+
# set Beetle log level to info, less noisy than debug
|
15
|
+
Beetle.config.logger.level = Logger::INFO
|
16
|
+
|
17
|
+
# setup client
|
18
|
+
client = Beetle::Client.new
|
19
|
+
client.register_message(:test)
|
20
|
+
|
21
|
+
n = 0
|
22
|
+
EM.run do
|
23
|
+
EM.add_periodic_timer(0.1) do
|
24
|
+
data = (n+=1)
|
25
|
+
client.logger.info "publishing #{data}"
|
26
|
+
client.publish(:test, data)
|
27
|
+
end
|
28
|
+
trap("INT") do
|
29
|
+
client.stop_publishing
|
30
|
+
EM.stop_event_loop
|
31
|
+
end
|
32
|
+
end
|
data/lib/beetle/client.rb
CHANGED
@@ -140,8 +140,7 @@ module Beetle
|
|
140
140
|
# For details on handler classes see class Beetle::Handler
|
141
141
|
|
142
142
|
def register_handler(queues, *args, &block)
|
143
|
-
queues = Array(queues)
|
144
|
-
queues.each {|q| raise UnknownQueue.new(q) unless self.queues.include?(q)}
|
143
|
+
queues = determine_queue_names(Array(queues))
|
145
144
|
opts = args.last.is_a?(Hash) ? args.pop : {}
|
146
145
|
handler = args.shift
|
147
146
|
raise ArgumentError.new("too many arguments for handler registration") unless args.empty?
|
@@ -167,9 +166,9 @@ module Beetle
|
|
167
166
|
end
|
168
167
|
|
169
168
|
# publishes a message. the given options hash is merged with options given on message registration.
|
169
|
+
# WARNING: empty message bodies can lead to problems.
|
170
170
|
def publish(message_name, data=nil, opts={})
|
171
|
-
message_name = message_name
|
172
|
-
raise UnknownMessage.new("unknown message #{message_name}") unless messages.include?(message_name)
|
171
|
+
message_name = validated_message_name(message_name)
|
173
172
|
publisher.publish(message_name, data, opts)
|
174
173
|
end
|
175
174
|
|
@@ -177,16 +176,14 @@ module Beetle
|
|
177
176
|
#
|
178
177
|
# unexpected behavior can ensue if the message gets routed to more than one recipient, so be careful.
|
179
178
|
def rpc(message_name, data=nil, opts={})
|
180
|
-
message_name = message_name
|
181
|
-
raise UnknownMessage.new("unknown message #{message_name}") unless messages.include?(message_name)
|
179
|
+
message_name = validated_message_name(message_name)
|
182
180
|
publisher.rpc(message_name, data, opts)
|
183
181
|
end
|
184
182
|
|
185
|
-
# purges the given
|
186
|
-
def purge(
|
187
|
-
|
188
|
-
|
189
|
-
publisher.purge(queue_name)
|
183
|
+
# purges the given queues on all configured servers
|
184
|
+
def purge(*queues)
|
185
|
+
queues = determine_queue_names(queues)
|
186
|
+
publisher.purge(queues)
|
190
187
|
end
|
191
188
|
|
192
189
|
# start listening to all registered queues. Calls #listen_queues internally
|
@@ -198,7 +195,8 @@ module Beetle
|
|
198
195
|
|
199
196
|
# start listening to a list of queues (default to all registered queues).
|
200
197
|
# runs the given block before entering the eventmachine loop.
|
201
|
-
def listen_queues(queues
|
198
|
+
def listen_queues(*queues, &block)
|
199
|
+
queues = determine_queue_names(queues)
|
202
200
|
subscriber.listen_queues(queues, &block)
|
203
201
|
end
|
204
202
|
|
@@ -212,6 +210,18 @@ module Beetle
|
|
212
210
|
publisher.stop
|
213
211
|
end
|
214
212
|
|
213
|
+
# pause listening on a list of queues
|
214
|
+
def pause_listening(*queues)
|
215
|
+
queues = determine_queue_names(queues)
|
216
|
+
subscriber.pause_listening(queues)
|
217
|
+
end
|
218
|
+
|
219
|
+
# resume listening on a list of queues
|
220
|
+
def resume_listening(*queues)
|
221
|
+
queues = determine_queue_names(queues)
|
222
|
+
subscriber.resume_listening(queues)
|
223
|
+
end
|
224
|
+
|
215
225
|
# traces queues without consuming them. useful for debugging message flow.
|
216
226
|
def trace(queue_names=self.queues.keys, &block)
|
217
227
|
queues_to_trace = self.queues.slice(*queue_names)
|
@@ -238,6 +248,26 @@ module Beetle
|
|
238
248
|
|
239
249
|
private
|
240
250
|
|
251
|
+
def determine_queue_names(queues)
|
252
|
+
if queues.empty?
|
253
|
+
self.queues.keys
|
254
|
+
else
|
255
|
+
queues.flatten.map{|q| validated_queue_name(q)}
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
def validated_queue_name(queue_name)
|
260
|
+
queue_name = queue_name.to_s
|
261
|
+
raise UnknownQueue.new("unknown queue #{queue_name}") unless queues.include?(queue_name)
|
262
|
+
queue_name
|
263
|
+
end
|
264
|
+
|
265
|
+
def validated_message_name(message_name)
|
266
|
+
message_name = message_name.to_s
|
267
|
+
raise UnknownMessage.new("unknown message #{message_name}") unless messages.include?(message_name)
|
268
|
+
message_name
|
269
|
+
end
|
270
|
+
|
241
271
|
class Configurator #:nodoc:all
|
242
272
|
def initialize(client, options={})
|
243
273
|
@client = client
|
data/lib/beetle/handler.rb
CHANGED
@@ -11,13 +11,16 @@ module Beetle
|
|
11
11
|
# the Message instance which caused the handler to be created
|
12
12
|
attr_reader :message
|
13
13
|
|
14
|
-
def self.create(
|
15
|
-
if
|
16
|
-
|
17
|
-
|
18
|
-
|
14
|
+
def self.create(handler, opts={}) #:nodoc:
|
15
|
+
if handler.is_a? Handler
|
16
|
+
# a handler instance
|
17
|
+
handler
|
18
|
+
elsif handler.is_a?(Class) && handler.ancestors.include?(Handler)
|
19
|
+
# handler class
|
20
|
+
handler.new
|
19
21
|
else
|
20
|
-
|
22
|
+
# presumably something which responds to call
|
23
|
+
new(handler, opts)
|
21
24
|
end
|
22
25
|
end
|
23
26
|
|
@@ -70,6 +73,13 @@ module Beetle
|
|
70
73
|
Beetle::reraise_expectation_errors!
|
71
74
|
end
|
72
75
|
|
76
|
+
# should not be overriden in subclasses
|
77
|
+
def processing_completed
|
78
|
+
completed
|
79
|
+
rescue Exception
|
80
|
+
Beetle::reraise_expectation_errors!
|
81
|
+
end
|
82
|
+
|
73
83
|
# called when handler execution raised an exception and no error callback was
|
74
84
|
# specified when the handler instance was created
|
75
85
|
def error(exception)
|
@@ -83,6 +93,12 @@ module Beetle
|
|
83
93
|
logger.error "Beetle: handler has finally failed"
|
84
94
|
end
|
85
95
|
|
96
|
+
# called after all normal processing has been completed. flushes the loggger, if it responds to flush.
|
97
|
+
def completed
|
98
|
+
logger.debug "Beetle: message processing completed"
|
99
|
+
logger.flush if logger.respond_to?(:flush)
|
100
|
+
end
|
101
|
+
|
86
102
|
# returns the configured Beetle logger
|
87
103
|
def self.logger
|
88
104
|
Beetle.config.logger
|
data/lib/beetle/publisher.rb
CHANGED
@@ -16,7 +16,7 @@ module Beetle
|
|
16
16
|
Bunny::ConnectionError, Bunny::ForcedChannelCloseError, Bunny::ForcedConnectionCloseError,
|
17
17
|
Bunny::MessageError, Bunny::ProtocolError, Bunny::ServerDownError, Bunny::UnsubscribeError,
|
18
18
|
Bunny::AcknowledgementError, Qrack::BufferOverflowError, Qrack::InvalidTypeError,
|
19
|
-
Errno::EHOSTUNREACH, Errno::ECONNRESET
|
19
|
+
Errno::EHOSTUNREACH, Errno::ECONNRESET, Timeout::Error
|
20
20
|
]
|
21
21
|
end
|
22
22
|
|
@@ -130,8 +130,12 @@ module Beetle
|
|
130
130
|
[status, result]
|
131
131
|
end
|
132
132
|
|
133
|
-
def purge(
|
134
|
-
each_server
|
133
|
+
def purge(queue_names) #:nodoc:
|
134
|
+
each_server do
|
135
|
+
queue_names.each do |name|
|
136
|
+
queue(name).purge rescue nil
|
137
|
+
end
|
138
|
+
end
|
135
139
|
end
|
136
140
|
|
137
141
|
def stop #:nodoc:
|
@@ -210,7 +214,7 @@ module Beetle
|
|
210
214
|
end
|
211
215
|
end
|
212
216
|
rescue Exception => e
|
213
|
-
logger.
|
217
|
+
logger.warn "Beetle: error closing down bunny #{e}"
|
214
218
|
Beetle::reraise_expectation_errors!
|
215
219
|
ensure
|
216
220
|
@bunnies[@server] = nil
|
data/lib/beetle/subscriber.rb
CHANGED
@@ -12,6 +12,7 @@ module Beetle
|
|
12
12
|
@handlers = {}
|
13
13
|
@amqp_connections = {}
|
14
14
|
@mqs = {}
|
15
|
+
@subscriptions = {}
|
15
16
|
end
|
16
17
|
|
17
18
|
# the client calls this method to subscribe to a list of queues.
|
@@ -32,6 +33,17 @@ module Beetle
|
|
32
33
|
end
|
33
34
|
end
|
34
35
|
|
36
|
+
def pause_listening(queues)
|
37
|
+
each_server do
|
38
|
+
queues.each { |name| pause(name) if has_subscription?(name) }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def resume_listening(queues)
|
43
|
+
each_server do
|
44
|
+
queues.each { |name| resume(name) if has_subscription?(name) }
|
45
|
+
end
|
46
|
+
end
|
35
47
|
|
36
48
|
# closes all AMQP connections and stops the eventmachine loop
|
37
49
|
def stop! #:nodoc:
|
@@ -39,6 +51,7 @@ module Beetle
|
|
39
51
|
EM.stop_event_loop
|
40
52
|
else
|
41
53
|
server, connection = @amqp_connections.shift
|
54
|
+
logger.debug "Beetle: closing connection to #{server}"
|
42
55
|
connection.close { stop! }
|
43
56
|
end
|
44
57
|
end
|
@@ -56,10 +69,6 @@ module Beetle
|
|
56
69
|
@client.bindings.slice(*queues).map{|_, opts| opts.map{|opt| opt[:exchange]}}.flatten.uniq
|
57
70
|
end
|
58
71
|
|
59
|
-
def exchanges_for_messages(messages)
|
60
|
-
@client.messages.slice(*messages).map{|_, opts| opts[:exchange]}.uniq
|
61
|
-
end
|
62
|
-
|
63
72
|
def queues_for_exchanges(exchanges)
|
64
73
|
@client.exchanges.slice(*exchanges).map{|_, opts| opts[:queues]}.flatten.compact.uniq
|
65
74
|
end
|
@@ -91,24 +100,46 @@ module Beetle
|
|
91
100
|
@mqs[server] ||= MQ.new(amqp_connection).prefetch(1)
|
92
101
|
end
|
93
102
|
|
103
|
+
def subscriptions(server=@server)
|
104
|
+
@subscriptions[server] ||= {}
|
105
|
+
end
|
106
|
+
|
107
|
+
def has_subscription?(name)
|
108
|
+
subscriptions.include?(name)
|
109
|
+
end
|
110
|
+
|
94
111
|
def subscribe(queue_name)
|
95
112
|
error("no handler for queue #{queue_name}") unless @handlers.include?(queue_name)
|
96
113
|
opts, handler = @handlers[queue_name]
|
97
114
|
queue_opts = @client.queues[queue_name][:amqp_name]
|
98
115
|
amqp_queue_name = queue_opts
|
99
116
|
callback = create_subscription_callback(queue_name, amqp_queue_name, handler, opts)
|
117
|
+
keys = opts.slice(*SUBSCRIPTION_KEYS).merge(:key => "#", :ack => true)
|
100
118
|
logger.debug "Beetle: subscribing to queue #{amqp_queue_name} with key # on server #{@server}"
|
101
119
|
begin
|
102
|
-
queues[queue_name].subscribe(
|
120
|
+
queues[queue_name].subscribe(keys, &callback)
|
121
|
+
subscriptions[queue_name] = [keys, callback]
|
103
122
|
rescue MQ::Error
|
104
123
|
error("Beetle: binding multiple handlers for the same queue isn't possible.")
|
105
124
|
end
|
106
125
|
end
|
107
126
|
|
127
|
+
def pause(queue_name)
|
128
|
+
return unless queues[queue_name].subscribed?
|
129
|
+
queues[queue_name].unsubscribe
|
130
|
+
end
|
131
|
+
|
132
|
+
def resume(queue_name)
|
133
|
+
return if queues[queue_name].subscribed?
|
134
|
+
keys, callback = subscriptions[queue_name]
|
135
|
+
queues[queue_name].subscribe(keys, &callback)
|
136
|
+
end
|
137
|
+
|
108
138
|
def create_subscription_callback(queue_name, amqp_queue_name, handler, opts)
|
109
139
|
server = @server
|
110
140
|
lambda do |header, data|
|
111
141
|
begin
|
142
|
+
# logger.debug "Beetle: received message"
|
112
143
|
processor = Handler.create(handler, opts)
|
113
144
|
message_options = opts.merge(:server => server, :store => @client.deduplication_store)
|
114
145
|
m = Message.new(amqp_queue_name, header, data, message_options)
|
@@ -124,10 +155,14 @@ module Beetle
|
|
124
155
|
exchange = MQ::Exchange.new(mq(server), :direct, "", :key => reply_to)
|
125
156
|
exchange.publish(m.handler_result.to_s, :headers => {:status => status})
|
126
157
|
end
|
158
|
+
# logger.debug "Beetle: processed message"
|
127
159
|
rescue Exception
|
128
160
|
Beetle::reraise_expectation_errors!
|
129
161
|
# swallow all exceptions
|
130
162
|
logger.error "Beetle: internal error during message processing: #{$!}: #{$!.backtrace.join("\n")}"
|
163
|
+
ensure
|
164
|
+
# processing_completed swallows all exceptions, so we don't need to protect this call
|
165
|
+
processor.processing_completed
|
131
166
|
end
|
132
167
|
end
|
133
168
|
end
|
@@ -149,7 +184,7 @@ module Beetle
|
|
149
184
|
|
150
185
|
def new_amqp_connection
|
151
186
|
# FIXME: wtf, how to test that reconnection feature....
|
152
|
-
con = AMQP.connect(:host => current_host, :port => current_port,
|
187
|
+
con = AMQP.connect(:host => current_host, :port => current_port, :logging => false,
|
153
188
|
:user => Beetle.config.user, :pass => Beetle.config.password, :vhost => Beetle.config.vhost)
|
154
189
|
con.instance_variable_set("@on_disconnect", proc{ con.__send__(:reconnect) })
|
155
190
|
con
|
data/lib/beetle.rb
CHANGED
data/test/beetle/client_test.rb
CHANGED
@@ -236,14 +236,14 @@ module Beetle
|
|
236
236
|
test "should delegate queue purging to the publisher instance" do
|
237
237
|
client = Client.new
|
238
238
|
client.register_queue(:queue)
|
239
|
-
client.send(:publisher).expects(:purge).with("queue").returns("ha!")
|
239
|
+
client.send(:publisher).expects(:purge).with(["queue"]).returns("ha!")
|
240
240
|
assert_equal "ha!", client.purge("queue")
|
241
241
|
end
|
242
242
|
|
243
243
|
test "purging a queue should convert the queue name to a string" do
|
244
244
|
client = Client.new
|
245
245
|
client.register_queue(:queue)
|
246
|
-
client.send(:publisher).expects(:purge).with("queue").returns("ha!")
|
246
|
+
client.send(:publisher).expects(:purge).with(["queue"]).returns("ha!")
|
247
247
|
assert_equal "ha!", client.purge(:queue)
|
248
248
|
end
|
249
249
|
|
@@ -251,6 +251,14 @@ module Beetle
|
|
251
251
|
assert_raises(UnknownQueue) { Client.new.purge(:mumu) }
|
252
252
|
end
|
253
253
|
|
254
|
+
test "should be possible to purge multiple queues with a single call" do
|
255
|
+
client = Client.new
|
256
|
+
client.register_queue(:queue1)
|
257
|
+
client.register_queue(:queue2)
|
258
|
+
client.send(:publisher).expects(:purge).with(["queue1","queue2"]).returns("ha!")
|
259
|
+
assert_equal "ha!", client.purge(:queue1, :queue2)
|
260
|
+
end
|
261
|
+
|
254
262
|
test "should delegate rpc calls to the publisher instance" do
|
255
263
|
client = Client.new
|
256
264
|
client.register_message("deadletter")
|
@@ -274,7 +282,7 @@ module Beetle
|
|
274
282
|
test "should delegate listening to queues to the subscriber instance" do
|
275
283
|
client = Client.new
|
276
284
|
client.register_queue(:test)
|
277
|
-
client.send(:subscriber).expects(:listen_queues).with([
|
285
|
+
client.send(:subscriber).expects(:listen_queues).with(['test']).yields
|
278
286
|
client.listen_queues([:test])
|
279
287
|
end
|
280
288
|
|
@@ -283,12 +291,36 @@ module Beetle
|
|
283
291
|
assert_raises(UnknownQueue) { Client.new.listen_queues([:foobar])}
|
284
292
|
end
|
285
293
|
|
294
|
+
test "trying to pause listening on an unknown queue should raise an exception" do
|
295
|
+
client = Client.new
|
296
|
+
assert_raises(UnknownQueue) { Client.new.pause_listening(:foobar)}
|
297
|
+
end
|
298
|
+
|
299
|
+
test "trying to resume listening on an unknown queue should raise an exception" do
|
300
|
+
client = Client.new
|
301
|
+
assert_raises(UnknownQueue) { Client.new.pause_listening(:foobar)}
|
302
|
+
end
|
303
|
+
|
286
304
|
test "should delegate stop_listening to the subscriber instance" do
|
287
305
|
client = Client.new
|
288
306
|
client.send(:subscriber).expects(:stop!)
|
289
307
|
client.stop_listening
|
290
308
|
end
|
291
309
|
|
310
|
+
test "should delegate pause_listening to the subscriber instance" do
|
311
|
+
client = Client.new
|
312
|
+
client.register_queue(:test)
|
313
|
+
client.send(:subscriber).expects(:pause_listening).with(%w(test))
|
314
|
+
client.pause_listening(:test)
|
315
|
+
end
|
316
|
+
|
317
|
+
test "should delegate resume_listening to the subscriber instance" do
|
318
|
+
client = Client.new
|
319
|
+
client.register_queue(:test)
|
320
|
+
client.send(:subscriber).expects(:resume_listening).with(%w(test))
|
321
|
+
client.resume_listening(:test)
|
322
|
+
end
|
323
|
+
|
292
324
|
test "should delegate handler registration to the subscriber instance" do
|
293
325
|
client = Client.new
|
294
326
|
client.register_queue("huhu")
|
data/test/beetle/handler_test.rb
CHANGED
@@ -77,17 +77,18 @@ module Beetle
|
|
77
77
|
assert_equal Beetle.config.logger, Handler.logger
|
78
78
|
end
|
79
79
|
|
80
|
-
test "default implementation of error and process and failure should not crash" do
|
80
|
+
test "default implementation of error and process and failure and completed should not crash" do
|
81
81
|
handler = Handler.create(lambda {})
|
82
82
|
handler.process
|
83
83
|
handler.error('barfoo')
|
84
84
|
handler.failure('razzmatazz')
|
85
|
+
handler.completed
|
85
86
|
end
|
86
87
|
|
87
88
|
test "should silently rescue exceptions in the process_exception call" do
|
88
89
|
mock = mock('error handler')
|
89
90
|
e = Exception.new
|
90
|
-
mock.expects(:call).with('message', e).raises(
|
91
|
+
mock.expects(:call).with('message', e).raises(Exception)
|
91
92
|
handler = Handler.create(lambda {}, :errback => mock)
|
92
93
|
handler.instance_variable_set(:@message, 'message')
|
93
94
|
assert_nothing_raised {handler.process_exception(e)}
|
@@ -95,11 +96,17 @@ module Beetle
|
|
95
96
|
|
96
97
|
test "should silently rescue exceptions in the process_failure call" do
|
97
98
|
mock = mock('failure handler')
|
98
|
-
mock.expects(:call).with('message', 1).raises(
|
99
|
+
mock.expects(:call).with('message', 1).raises(Exception)
|
99
100
|
handler = Handler.create(lambda {}, :failback => mock)
|
100
101
|
handler.instance_variable_set(:@message, 'message')
|
101
102
|
assert_nothing_raised {handler.process_failure(1)}
|
102
103
|
end
|
103
104
|
|
105
|
+
test "should silently rescue exceptions in the processing_completed call" do
|
106
|
+
handler = Handler.create(lambda {})
|
107
|
+
handler.expects(:completed).raises(Exception)
|
108
|
+
assert_nothing_raised {handler.processing_completed}
|
109
|
+
end
|
110
|
+
|
104
111
|
end
|
105
112
|
end
|
@@ -274,7 +274,7 @@ module Beetle
|
|
274
274
|
@pub.expects(:set_current_server).with("b").in_sequence(s)
|
275
275
|
@pub.expects(:queue).with("queue").returns(queue).in_sequence(s)
|
276
276
|
queue.expects(:purge).in_sequence(s)
|
277
|
-
@pub.purge("queue")
|
277
|
+
@pub.purge(["queue"])
|
278
278
|
end
|
279
279
|
end
|
280
280
|
|
@@ -27,7 +27,7 @@ module Beetle
|
|
27
27
|
m = mock("dummy")
|
28
28
|
expected_amqp_options = {
|
29
29
|
:host => @sub.send(:current_host), :port => @sub.send(:current_port),
|
30
|
-
:user => "guest", :pass => "guest", :vhost => "/"
|
30
|
+
:user => "guest", :pass => "guest", :vhost => "/", :logging => false
|
31
31
|
}
|
32
32
|
AMQP.expects(:connect).with(expected_amqp_options).returns(m)
|
33
33
|
# TODO: smarter way to test? what triggers the amqp_connection private method call?
|
@@ -56,6 +56,75 @@ module Beetle
|
|
56
56
|
|
57
57
|
end
|
58
58
|
|
59
|
+
class SubscriberPauseAndResumeTest < Test::Unit::TestCase
|
60
|
+
def setup
|
61
|
+
@client = Client.new
|
62
|
+
@sub = @client.send(:subscriber)
|
63
|
+
@sub.servers << "localhost:7777"
|
64
|
+
@server1, @server2 = @sub.servers
|
65
|
+
@client.register_message(:a)
|
66
|
+
@client.register_queue(:a)
|
67
|
+
@client.register_handler(%W(a)){}
|
68
|
+
end
|
69
|
+
|
70
|
+
test "should pause on all servers when a handler has been registered" do
|
71
|
+
@sub.expects(:pause).with("a").times(2)
|
72
|
+
@sub.stubs(:has_subscription?).returns(true)
|
73
|
+
@sub.pause_listening(%w(a))
|
74
|
+
end
|
75
|
+
|
76
|
+
test "should resume on all servers when a handler has been registered" do
|
77
|
+
@sub.expects(:resume).with("a").times(2)
|
78
|
+
@sub.stubs(:has_subscription?).returns(true)
|
79
|
+
@sub.resume_listening(%w(a))
|
80
|
+
end
|
81
|
+
|
82
|
+
test "should pause on no servers when no handler has been registered" do
|
83
|
+
@sub.expects(:pause).never
|
84
|
+
@sub.stubs(:has_subscription?).returns(false)
|
85
|
+
@sub.pause_listening(%w(a))
|
86
|
+
end
|
87
|
+
|
88
|
+
test "should resume on no servers when no handler has been registered" do
|
89
|
+
@sub.expects(:resume).never
|
90
|
+
@sub.stubs(:has_subscription?).returns(false)
|
91
|
+
@sub.resume_listening(%w(a))
|
92
|
+
end
|
93
|
+
|
94
|
+
test "pausing a single queue should call amqp unsubscribe" do
|
95
|
+
q = mock("queue a")
|
96
|
+
q.expects(:subscribed?).returns(true)
|
97
|
+
q.expects(:unsubscribe)
|
98
|
+
@sub.stubs(:queues).returns({"a" =>q})
|
99
|
+
@sub.__send__(:pause, "a")
|
100
|
+
end
|
101
|
+
|
102
|
+
test "pausing a single queue which is already paused should not call amqp unsubscribe" do
|
103
|
+
q = mock("queue a")
|
104
|
+
q.expects(:subscribed?).returns(false)
|
105
|
+
q.expects(:unsubscribe).never
|
106
|
+
@sub.stubs(:queues).returns({"a" =>q})
|
107
|
+
@sub.__send__(:pause, "a")
|
108
|
+
end
|
109
|
+
|
110
|
+
test "resuming a single queue should call amqp subscribe" do
|
111
|
+
q = mock("queue a")
|
112
|
+
q.expects(:subscribed?).returns(false)
|
113
|
+
q.expects(:subscribe)
|
114
|
+
@sub.stubs(:queues).returns({"a" =>q})
|
115
|
+
@sub.__send__(:resume, "a")
|
116
|
+
end
|
117
|
+
|
118
|
+
test "resuming a single queue which is already subscribed should not call amqp subscribe" do
|
119
|
+
q = mock("queue a")
|
120
|
+
q.expects(:subscribed?).returns(true)
|
121
|
+
q.expects(:subscribe).never
|
122
|
+
@sub.stubs(:queues).returns({"a" =>q})
|
123
|
+
@sub.__send__(:resume, "a")
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
|
59
128
|
class AdditionalSubscriptionServersTest < Test::Unit::TestCase
|
60
129
|
def setup
|
61
130
|
@config = Configuration.new
|
@@ -160,6 +229,8 @@ module Beetle
|
|
160
229
|
@sub = client.send(:subscriber)
|
161
230
|
@exception = Exception.new "murks"
|
162
231
|
@handler = Handler.create(lambda{|*args| raise @exception})
|
232
|
+
# handler method 'processing_completed' should be called under all circumstances
|
233
|
+
@handler.expects(:processing_completed).once
|
163
234
|
@callback = @sub.send(:create_subscription_callback, "my myessage", @queue, @handler, :exceptions => 1)
|
164
235
|
end
|
165
236
|
|
@@ -225,7 +296,7 @@ module Beetle
|
|
225
296
|
@sub.send(:subscribe_queues, %W(a b))
|
226
297
|
end
|
227
298
|
|
228
|
-
test "subscribe should subscribe with a subscription callback created from the registered block" do
|
299
|
+
test "subscribe should subscribe with a subscription callback created from the registered block and remember the subscription" do
|
229
300
|
@client.register_queue(:some_queue, :exchange => "some_exchange", :key => "some_key")
|
230
301
|
server = @sub.server
|
231
302
|
header = header_with_params({})
|
@@ -239,17 +310,21 @@ module Beetle
|
|
239
310
|
end
|
240
311
|
@sub.register_handler("some_queue", &proc)
|
241
312
|
q = mock("QUEUE")
|
242
|
-
|
243
|
-
|
313
|
+
subscription_options = {:ack => true, :key => "#"}
|
314
|
+
q.expects(:subscribe).with(subscription_options).yields(header, "foo")
|
315
|
+
@sub.expects(:queues).returns({"some_queue" => q}).twice
|
244
316
|
@sub.send(:subscribe, "some_queue")
|
245
317
|
assert block_called
|
318
|
+
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") }
|
246
321
|
end
|
247
322
|
|
248
323
|
test "subscribe should fail if no handler exists for given message" do
|
249
324
|
assert_raises(Error){ @sub.send(:subscribe, "some_queue") }
|
250
325
|
end
|
251
326
|
|
252
|
-
test "
|
327
|
+
test "listening on queues should use eventmachine. create exchanges. bind queues. install subscribers. and yield." do
|
253
328
|
@client.register_exchange(:an_exchange)
|
254
329
|
@client.register_queue(:a_queue, :exchange => :an_exchange)
|
255
330
|
@client.register_message(:a_message, :key => "foo", :exchange => :an_exchange)
|
metadata
CHANGED
@@ -1,13 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: beetle
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
5
|
-
prerelease:
|
4
|
+
hash: 15424039
|
5
|
+
prerelease: 6
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
|
8
|
+
- 3
|
9
|
+
- 0
|
10
|
+
- rc
|
11
|
+
- 1
|
12
|
+
version: 0.3.0.rc.1
|
11
13
|
platform: ruby
|
12
14
|
authors:
|
13
15
|
- Stefan Kaes
|
@@ -18,7 +20,7 @@ autorequire:
|
|
18
20
|
bindir: bin
|
19
21
|
cert_chain: []
|
20
22
|
|
21
|
-
date: 2011-
|
23
|
+
date: 2011-08-27 00:00:00 +02:00
|
22
24
|
default_executable: beetle
|
23
25
|
dependencies:
|
24
26
|
- !ruby/object:Gem::Dependency
|
@@ -43,36 +45,20 @@ dependencies:
|
|
43
45
|
requirement: &id002 !ruby/object:Gem::Requirement
|
44
46
|
none: false
|
45
47
|
requirements:
|
46
|
-
- -
|
48
|
+
- - ~>
|
47
49
|
- !ruby/object:Gem::Version
|
48
|
-
hash:
|
50
|
+
hash: 1
|
49
51
|
segments:
|
50
52
|
- 0
|
51
|
-
-
|
52
|
-
-
|
53
|
-
version: 0.
|
53
|
+
- 7
|
54
|
+
- 1
|
55
|
+
version: 0.7.1
|
54
56
|
type: :runtime
|
55
57
|
version_requirements: *id002
|
56
|
-
- !ruby/object:Gem::Dependency
|
57
|
-
name: bunny-ext
|
58
|
-
prerelease: false
|
59
|
-
requirement: &id003 !ruby/object:Gem::Requirement
|
60
|
-
none: false
|
61
|
-
requirements:
|
62
|
-
- - ">="
|
63
|
-
- !ruby/object:Gem::Version
|
64
|
-
hash: 13
|
65
|
-
segments:
|
66
|
-
- 0
|
67
|
-
- 6
|
68
|
-
- 5
|
69
|
-
version: 0.6.5
|
70
|
-
type: :runtime
|
71
|
-
version_requirements: *id003
|
72
58
|
- !ruby/object:Gem::Dependency
|
73
59
|
name: redis
|
74
60
|
prerelease: false
|
75
|
-
requirement: &
|
61
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
76
62
|
none: false
|
77
63
|
requirements:
|
78
64
|
- - "="
|
@@ -84,11 +70,11 @@ dependencies:
|
|
84
70
|
- 4
|
85
71
|
version: 2.0.4
|
86
72
|
type: :runtime
|
87
|
-
version_requirements: *
|
73
|
+
version_requirements: *id003
|
88
74
|
- !ruby/object:Gem::Dependency
|
89
75
|
name: amqp
|
90
76
|
prerelease: false
|
91
|
-
requirement: &
|
77
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
92
78
|
none: false
|
93
79
|
requirements:
|
94
80
|
- - ~>
|
@@ -100,11 +86,11 @@ dependencies:
|
|
100
86
|
- 7
|
101
87
|
version: 0.6.7
|
102
88
|
type: :runtime
|
103
|
-
version_requirements: *
|
89
|
+
version_requirements: *id004
|
104
90
|
- !ruby/object:Gem::Dependency
|
105
91
|
name: activesupport
|
106
92
|
prerelease: false
|
107
|
-
requirement: &
|
93
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
108
94
|
none: false
|
109
95
|
requirements:
|
110
96
|
- - ">="
|
@@ -116,11 +102,11 @@ dependencies:
|
|
116
102
|
- 4
|
117
103
|
version: 2.3.4
|
118
104
|
type: :runtime
|
119
|
-
version_requirements: *
|
105
|
+
version_requirements: *id005
|
120
106
|
- !ruby/object:Gem::Dependency
|
121
107
|
name: daemons
|
122
108
|
prerelease: false
|
123
|
-
requirement: &
|
109
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
124
110
|
none: false
|
125
111
|
requirements:
|
126
112
|
- - ">="
|
@@ -132,11 +118,11 @@ dependencies:
|
|
132
118
|
- 10
|
133
119
|
version: 1.0.10
|
134
120
|
type: :runtime
|
135
|
-
version_requirements: *
|
121
|
+
version_requirements: *id006
|
136
122
|
- !ruby/object:Gem::Dependency
|
137
123
|
name: rake
|
138
124
|
prerelease: false
|
139
|
-
requirement: &
|
125
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
140
126
|
none: false
|
141
127
|
requirements:
|
142
128
|
- - ">="
|
@@ -148,11 +134,11 @@ dependencies:
|
|
148
134
|
- 7
|
149
135
|
version: 0.8.7
|
150
136
|
type: :development
|
151
|
-
version_requirements: *
|
137
|
+
version_requirements: *id007
|
152
138
|
- !ruby/object:Gem::Dependency
|
153
139
|
name: mocha
|
154
140
|
prerelease: false
|
155
|
-
requirement: &
|
141
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
156
142
|
none: false
|
157
143
|
requirements:
|
158
144
|
- - ">="
|
@@ -162,11 +148,11 @@ dependencies:
|
|
162
148
|
- 0
|
163
149
|
version: "0"
|
164
150
|
type: :development
|
165
|
-
version_requirements: *
|
151
|
+
version_requirements: *id008
|
166
152
|
- !ruby/object:Gem::Dependency
|
167
153
|
name: rcov
|
168
154
|
prerelease: false
|
169
|
-
requirement: &
|
155
|
+
requirement: &id009 !ruby/object:Gem::Requirement
|
170
156
|
none: false
|
171
157
|
requirements:
|
172
158
|
- - ">="
|
@@ -176,11 +162,11 @@ dependencies:
|
|
176
162
|
- 0
|
177
163
|
version: "0"
|
178
164
|
type: :development
|
179
|
-
version_requirements: *
|
165
|
+
version_requirements: *id009
|
180
166
|
- !ruby/object:Gem::Dependency
|
181
167
|
name: redgreen
|
182
168
|
prerelease: false
|
183
|
-
requirement: &
|
169
|
+
requirement: &id010 !ruby/object:Gem::Requirement
|
184
170
|
none: false
|
185
171
|
requirements:
|
186
172
|
- - ">="
|
@@ -190,11 +176,11 @@ dependencies:
|
|
190
176
|
- 0
|
191
177
|
version: "0"
|
192
178
|
type: :development
|
193
|
-
version_requirements: *
|
179
|
+
version_requirements: *id010
|
194
180
|
- !ruby/object:Gem::Dependency
|
195
181
|
name: wirble
|
196
182
|
prerelease: false
|
197
|
-
requirement: &
|
183
|
+
requirement: &id011 !ruby/object:Gem::Requirement
|
198
184
|
none: false
|
199
185
|
requirements:
|
200
186
|
- - ">="
|
@@ -204,11 +190,11 @@ dependencies:
|
|
204
190
|
- 0
|
205
191
|
version: "0"
|
206
192
|
type: :development
|
207
|
-
version_requirements: *
|
193
|
+
version_requirements: *id011
|
208
194
|
- !ruby/object:Gem::Dependency
|
209
195
|
name: cucumber
|
210
196
|
prerelease: false
|
211
|
-
requirement: &
|
197
|
+
requirement: &id012 !ruby/object:Gem::Requirement
|
212
198
|
none: false
|
213
199
|
requirements:
|
214
200
|
- - ">="
|
@@ -220,11 +206,11 @@ dependencies:
|
|
220
206
|
- 2
|
221
207
|
version: 0.7.2
|
222
208
|
type: :development
|
223
|
-
version_requirements: *
|
209
|
+
version_requirements: *id012
|
224
210
|
- !ruby/object:Gem::Dependency
|
225
211
|
name: daemon_controller
|
226
212
|
prerelease: false
|
227
|
-
requirement: &
|
213
|
+
requirement: &id013 !ruby/object:Gem::Requirement
|
228
214
|
none: false
|
229
215
|
requirements:
|
230
216
|
- - ">="
|
@@ -234,7 +220,7 @@ dependencies:
|
|
234
220
|
- 0
|
235
221
|
version: "0"
|
236
222
|
type: :development
|
237
|
-
version_requirements: *
|
223
|
+
version_requirements: *id013
|
238
224
|
description: A highly available, reliable messaging infrastructure
|
239
225
|
email: developers@xing.com
|
240
226
|
executables:
|
@@ -254,9 +240,11 @@ files:
|
|
254
240
|
- examples/handling_exceptions.rb
|
255
241
|
- examples/multiple_exchanges.rb
|
256
242
|
- examples/multiple_queues.rb
|
243
|
+
- examples/pause_and_resume.rb
|
257
244
|
- examples/redundant.rb
|
258
245
|
- examples/rpc.rb
|
259
246
|
- examples/simple.rb
|
247
|
+
- examples/test_publisher.rb
|
260
248
|
- lib/beetle/base.rb
|
261
249
|
- lib/beetle/client.rb
|
262
250
|
- lib/beetle/commands/configuration_client.rb
|
@@ -287,7 +275,6 @@ files:
|
|
287
275
|
- features/support/test_daemons/redis_configuration_client.rb
|
288
276
|
- features/support/test_daemons/redis_configuration_server.rb
|
289
277
|
- script/console
|
290
|
-
- script/console~
|
291
278
|
- script/start_rabbit
|
292
279
|
- beetle.gemspec
|
293
280
|
- Rakefile
|
data/script/console~
DELETED