beetle 0.2.13 → 0.3.0.rc.1
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 +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