beetle 0.2.5 → 0.2.9.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/RELEASE_NOTES.rdoc +12 -0
- data/Rakefile +1 -0
- data/beetle.gemspec +3 -3
- data/examples/attempts.rb +13 -13
- data/lib/beetle/client.rb +4 -0
- data/lib/beetle/configuration.rb +12 -7
- data/lib/beetle/message.rb +1 -0
- data/lib/beetle/publisher.rb +17 -3
- data/lib/beetle/redis_configuration_client.rb +6 -0
- data/lib/beetle/redis_configuration_server.rb +20 -3
- data/lib/beetle/subscriber.rb +8 -2
- data/lib/beetle.rb +5 -0
- data/lib/ext/qrack/client.rb +28 -0
- data/test/beetle/client_test.rb +4 -0
- data/test/beetle/configuration_test.rb +2 -2
- data/test/beetle/deduplication_store_test.rb +4 -0
- data/test/beetle/ext_test.rb +26 -0
- data/test/beetle/message_test.rb +1 -1
- data/test/beetle/publisher_test.rb +14 -9
- data/test/beetle/redis_configuration_client_test.rb +10 -0
- data/test/beetle/redis_configuration_server_test.rb +9 -1
- data/test/beetle/subscriber_test.rb +16 -3
- data/test/test_helper.rb +12 -1
- metadata +10 -6
data/RELEASE_NOTES.rdoc
CHANGED
@@ -1,5 +1,17 @@
|
|
1
1
|
= Release Notes
|
2
2
|
|
3
|
+
== Version 0.2.9
|
4
|
+
|
5
|
+
* Beetle::Client now raises an exception when it fails to publish a message to at least 1 RabbitMQ server
|
6
|
+
* Subscribers are now stopped cleanly to avoid 'closed abruptly' messages in the RabbitMQ server log
|
7
|
+
* Added send and receive timeouts on the socket and use system_timer for ruby side timeouts
|
8
|
+
|
9
|
+
== Version 0.2.6
|
10
|
+
|
11
|
+
* Set dependency on ActiveSupport to 2.3.x since it ain't compatible to version 3.x yet
|
12
|
+
* Publishers catch a wider range (all?) of possible exceptions when publishing messages
|
13
|
+
* Redis Configuration Servers detect and warn when unknown Redis Configuration Clients connect
|
14
|
+
|
3
15
|
== Version 0.2.5
|
4
16
|
|
5
17
|
Added missing files to gem and rdoc
|
data/Rakefile
CHANGED
data/beetle.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = "beetle"
|
3
|
-
s.version = "0.2.
|
3
|
+
s.version = "0.2.9.1"
|
4
4
|
|
5
5
|
s.required_rubygems_version = ">= 1.3.1"
|
6
6
|
s.authors = ["Stefan Kaes", "Pascal Friederich", "Ali Jelveh", "Sebastian Roebke"]
|
@@ -31,9 +31,9 @@ Gem::Specification.new do |s|
|
|
31
31
|
s.specification_version = 3
|
32
32
|
s.add_runtime_dependency("uuid4r", [">= 0.1.1"])
|
33
33
|
s.add_runtime_dependency("bunny", [">= 0.6.0"])
|
34
|
-
s.add_runtime_dependency("redis", ["
|
34
|
+
s.add_runtime_dependency("redis", ["= 2.0.4"])
|
35
35
|
s.add_runtime_dependency("amqp", [">= 0.6.7"])
|
36
|
-
s.add_runtime_dependency("activesupport", ["
|
36
|
+
s.add_runtime_dependency("activesupport", ["~> 2.3.4"])
|
37
37
|
s.add_runtime_dependency("daemons", [">= 1.0.10"])
|
38
38
|
s.add_development_dependency("mocha", [">= 0"])
|
39
39
|
s.add_development_dependency("rcov", [">= 0"])
|
data/examples/attempts.rb
CHANGED
@@ -14,15 +14,15 @@ require File.expand_path("../lib/beetle", File.dirname(__FILE__))
|
|
14
14
|
Beetle.config.logger.level = Logger::INFO
|
15
15
|
|
16
16
|
# setup client
|
17
|
-
client = Beetle::Client.new
|
18
|
-
client.register_queue(:test)
|
19
|
-
client.register_message(:test)
|
17
|
+
$client = Beetle::Client.new
|
18
|
+
$client.register_queue(:test)
|
19
|
+
$client.register_message(:test)
|
20
20
|
|
21
21
|
# purge the test queue
|
22
|
-
client.purge(:test)
|
22
|
+
$client.purge(:test)
|
23
23
|
|
24
24
|
# empty the dedup store
|
25
|
-
client.deduplication_store.flushdb
|
25
|
+
$client.deduplication_store.flushdb
|
26
26
|
|
27
27
|
# we're starting with 0 exceptions and expect our handler to process the message until the exception count has reached 10
|
28
28
|
$exceptions = 0
|
@@ -32,35 +32,35 @@ $max_exceptions = 10
|
|
32
32
|
# in this example we've not only overwritten the process method but also the
|
33
33
|
# error and failure methods of the handler baseclass
|
34
34
|
class Handler < Beetle::Handler
|
35
|
-
|
35
|
+
|
36
36
|
# called when the handler receives the message - fail everytime
|
37
37
|
def process
|
38
38
|
raise "failed #{$exceptions += 1} times"
|
39
39
|
end
|
40
|
-
|
40
|
+
|
41
41
|
# called when handler process raised an exception
|
42
42
|
def error(exception)
|
43
43
|
logger.info "execution failed: #{exception}"
|
44
44
|
end
|
45
|
-
|
45
|
+
|
46
46
|
# called when the handler has finally failed
|
47
47
|
# we're stopping the event loop so this script stops after that
|
48
48
|
def failure(result)
|
49
49
|
super
|
50
|
-
|
50
|
+
$client.stop_listening
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
54
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 => $max_exceptions, :delay => 0)
|
55
|
+
$client.register_handler(:test, Handler, :exceptions => $max_exceptions, :delay => 0)
|
56
56
|
|
57
57
|
# publish a our test message
|
58
|
-
client.publish(:test, "snafu")
|
58
|
+
$client.publish(:test, "snafu")
|
59
59
|
|
60
60
|
# and start our listening loop...
|
61
|
-
client.listen
|
61
|
+
$client.listen
|
62
62
|
|
63
63
|
# error handling, if everything went right this shouldn't happen.
|
64
64
|
if $exceptions != $max_exceptions + 1
|
65
65
|
raise "something is fishy. Failed #{$exceptions} times"
|
66
|
-
end
|
66
|
+
end
|
data/lib/beetle/client.rb
CHANGED
@@ -24,6 +24,9 @@ module Beetle
|
|
24
24
|
# the AMQP servers available for publishing
|
25
25
|
attr_reader :servers
|
26
26
|
|
27
|
+
# additional AMQP servers available for subscribing. useful for migration scenarios.
|
28
|
+
attr_reader :additional_subscription_servers
|
29
|
+
|
27
30
|
# an options hash for the configured exchanges
|
28
31
|
attr_reader :exchanges
|
29
32
|
|
@@ -46,6 +49,7 @@ module Beetle
|
|
46
49
|
def initialize(config = Beetle.config)
|
47
50
|
@config = config
|
48
51
|
@servers = config.servers.split(/ *, */)
|
52
|
+
@additional_subscription_servers = config.additional_subscription_servers.split(/ *, */)
|
49
53
|
@exchanges = {}
|
50
54
|
@queues = {}
|
51
55
|
@messages = {}
|
data/lib/beetle/configuration.rb
CHANGED
@@ -38,6 +38,8 @@ module Beetle
|
|
38
38
|
|
39
39
|
# list of amqp servers to use (defaults to <tt>"localhost:5672"</tt>)
|
40
40
|
attr_accessor :servers
|
41
|
+
# list of additional amqp servers to use for subscribers (defaults to <tt>""</tt>)
|
42
|
+
attr_accessor :additional_subscription_servers
|
41
43
|
# the virtual host to use on the AMQP servers (defaults to <tt>"/"</tt>)
|
42
44
|
attr_accessor :vhost
|
43
45
|
# the AMQP user to use when connecting to the AMQP servers (defaults to <tt>"guest"</tt>)
|
@@ -63,6 +65,7 @@ module Beetle
|
|
63
65
|
self.redis_configuration_client_ids = ""
|
64
66
|
|
65
67
|
self.servers = "localhost:5672"
|
68
|
+
self.additional_subscription_servers = ""
|
66
69
|
self.vhost = "/"
|
67
70
|
self.user = "guest"
|
68
71
|
self.password = "guest"
|
@@ -77,13 +80,14 @@ module Beetle
|
|
77
80
|
end
|
78
81
|
|
79
82
|
def logger
|
80
|
-
@logger ||=
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
83
|
+
@logger ||=
|
84
|
+
begin
|
85
|
+
l = Logger.new(log_file)
|
86
|
+
l.formatter = Logger::Formatter.new
|
87
|
+
l.level = Logger::INFO
|
88
|
+
l.datetime_format = "%Y-%m-%d %H:%M:%S"
|
89
|
+
l
|
90
|
+
end
|
87
91
|
end
|
88
92
|
|
89
93
|
private
|
@@ -93,6 +97,7 @@ module Beetle
|
|
93
97
|
send("#{key}=", value)
|
94
98
|
end
|
95
99
|
rescue Exception
|
100
|
+
Beetle::reraise_expectation_errors!
|
96
101
|
logger.error "Error loading beetle config file '#{config_file}': #{$!}"
|
97
102
|
raise
|
98
103
|
end
|
data/lib/beetle/message.rb
CHANGED
data/lib/beetle/publisher.rb
CHANGED
@@ -9,6 +9,17 @@ module Beetle
|
|
9
9
|
@bunnies = {}
|
10
10
|
end
|
11
11
|
|
12
|
+
# list of exceptions potentially raised by bunny
|
13
|
+
# these need to be lazy, because qrack exceptions are only defined after a connection has been established
|
14
|
+
def bunny_exceptions
|
15
|
+
[
|
16
|
+
Bunny::ConnectionError, Bunny::ForcedChannelCloseError, Bunny::ForcedConnectionCloseError,
|
17
|
+
Bunny::MessageError, Bunny::ProtocolError, Bunny::ServerDownError, Bunny::UnsubscribeError,
|
18
|
+
Bunny::AcknowledgementError, Qrack::BufferOverflowError, Qrack::InvalidTypeError,
|
19
|
+
Errno::EHOSTUNREACH, Errno::ECONNRESET
|
20
|
+
]
|
21
|
+
end
|
22
|
+
|
12
23
|
def publish(message_name, data, opts={}) #:nodoc:
|
13
24
|
opts = @client.messages[message_name].merge(opts.symbolize_keys)
|
14
25
|
exchange_name = opts.delete(:exchange)
|
@@ -33,12 +44,13 @@ module Beetle
|
|
33
44
|
exchange(exchange_name).publish(data, opts)
|
34
45
|
logger.debug "Beetle: message sent!"
|
35
46
|
published = 1
|
36
|
-
rescue
|
47
|
+
rescue *bunny_exceptions
|
37
48
|
stop!
|
38
49
|
mark_server_dead
|
39
50
|
tries -= 1
|
40
51
|
retry if tries > 0
|
41
52
|
logger.error "Beetle: message could not be delivered: #{message_name}"
|
53
|
+
raise NoMessageSent.new
|
42
54
|
end
|
43
55
|
published
|
44
56
|
end
|
@@ -60,7 +72,7 @@ module Beetle
|
|
60
72
|
exchange(exchange_name).publish(data, opts)
|
61
73
|
published << @server
|
62
74
|
logger.debug "Beetle: message sent (#{published})!"
|
63
|
-
rescue
|
75
|
+
rescue *bunny_exceptions
|
64
76
|
stop!
|
65
77
|
mark_server_dead
|
66
78
|
end
|
@@ -68,9 +80,11 @@ module Beetle
|
|
68
80
|
case published.size
|
69
81
|
when 0
|
70
82
|
logger.error "Beetle: message could not be delivered: #{message_name}"
|
83
|
+
raise NoMessageSent.new
|
71
84
|
when 1
|
72
85
|
logger.warn "Beetle: failed to send message redundantly"
|
73
86
|
end
|
87
|
+
|
74
88
|
published.size
|
75
89
|
end
|
76
90
|
|
@@ -101,7 +115,7 @@ module Beetle
|
|
101
115
|
status = msg[:header].properties[:headers][:status]
|
102
116
|
end
|
103
117
|
logger.debug "Beetle: rpc complete!"
|
104
|
-
rescue
|
118
|
+
rescue *bunny_exceptions
|
105
119
|
stop!
|
106
120
|
mark_server_dead
|
107
121
|
tries -= 1
|
@@ -37,6 +37,7 @@ module Beetle
|
|
37
37
|
# loop, reacting to failover related messages sent by RedisConfigurationServer.
|
38
38
|
def start
|
39
39
|
verify_redis_master_file_string
|
40
|
+
client_started!
|
40
41
|
logger.info "RedisConfigurationClient starting (client id: #{id})"
|
41
42
|
determine_initial_master
|
42
43
|
clear_redis_master_file unless current_master.try(:master?)
|
@@ -102,6 +103,7 @@ module Beetle
|
|
102
103
|
config.message :client_invalidated
|
103
104
|
config.message :reconfigure
|
104
105
|
config.queue :reconfigure, :amqp_name => "#{system}_reconfigure_#{id}"
|
106
|
+
config.message :client_started
|
105
107
|
|
106
108
|
config.handler [:ping, :invalidate, :reconfigure], MessageDispatcher
|
107
109
|
end
|
@@ -118,6 +120,10 @@ module Beetle
|
|
118
120
|
beetle.publish(:pong, {"id" => id, "token" => @current_token}.to_json)
|
119
121
|
end
|
120
122
|
|
123
|
+
def client_started!
|
124
|
+
beetle.publish(:client_started, {"id" => id}.to_json)
|
125
|
+
end
|
126
|
+
|
121
127
|
def invalidate!
|
122
128
|
@current_master = nil
|
123
129
|
clear_redis_master_file
|
@@ -75,6 +75,17 @@ module Beetle
|
|
75
75
|
end
|
76
76
|
end
|
77
77
|
|
78
|
+
def client_started(payload)
|
79
|
+
id = payload["id"]
|
80
|
+
if client_id_valid?(id)
|
81
|
+
logger.info("Received client_started message from id #{id}")
|
82
|
+
else
|
83
|
+
msg = "Received client_started message from unknown id '#{id}'"
|
84
|
+
logger.error(msg)
|
85
|
+
beetle.publish(:system_notification, {"message" => msg}.to_json)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
78
89
|
# called by the message dispatcher when a "client_invalidated" message from a RedisConfigurationClient is received
|
79
90
|
def client_invalidated(payload)
|
80
91
|
id = payload["id"]
|
@@ -139,8 +150,10 @@ module Beetle
|
|
139
150
|
config.message :invalidate
|
140
151
|
config.message :reconfigure
|
141
152
|
config.message :system_notification
|
153
|
+
config.message :client_started
|
154
|
+
config.queue :client_started, :amqp_name => "#{system}_client_started"
|
142
155
|
|
143
|
-
config.handler [:pong, :client_invalidated], MessageDispatcher
|
156
|
+
config.handler [:pong, :client_invalidated, :client_started], MessageDispatcher
|
144
157
|
end
|
145
158
|
end
|
146
159
|
|
@@ -166,13 +179,17 @@ module Beetle
|
|
166
179
|
end
|
167
180
|
|
168
181
|
def validate_pong_client_id(client_id)
|
169
|
-
unless known_client =
|
170
|
-
msg = "Received pong message from unknown
|
182
|
+
unless known_client = client_id_valid?(client_id)
|
183
|
+
msg = "Received pong message from unknown id '#{client_id}'"
|
171
184
|
logger.error(msg)
|
172
185
|
beetle.publish(:system_notification, {"message" => msg}.to_json)
|
173
186
|
end
|
174
187
|
known_client
|
175
188
|
end
|
189
|
+
|
190
|
+
def client_id_valid?(client_id)
|
191
|
+
@client_ids.include?(client_id)
|
192
|
+
end
|
176
193
|
|
177
194
|
def redeem_token(token)
|
178
195
|
valid_token = token == @current_token
|
data/lib/beetle/subscriber.rb
CHANGED
@@ -5,6 +5,7 @@ module Beetle
|
|
5
5
|
# create a new subscriber instance
|
6
6
|
def initialize(client, options = {}) #:nodoc:
|
7
7
|
super
|
8
|
+
@servers.concat @client.additional_subscription_servers
|
8
9
|
@handlers = {}
|
9
10
|
@amqp_connections = {}
|
10
11
|
@mqs = {}
|
@@ -30,9 +31,14 @@ module Beetle
|
|
30
31
|
end
|
31
32
|
end
|
32
33
|
|
33
|
-
# stops the eventmachine loop
|
34
|
+
# closes all AMQP connections and stops the eventmachine loop
|
34
35
|
def stop! #:nodoc:
|
35
|
-
|
36
|
+
if @amqp_connections.empty?
|
37
|
+
EM.stop_event_loop
|
38
|
+
else
|
39
|
+
server, connection = @amqp_connections.shift
|
40
|
+
connection.close { stop! }
|
41
|
+
end
|
36
42
|
end
|
37
43
|
|
38
44
|
# register handler for the given queues (see Client#register_handler)
|
data/lib/beetle.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
$:.unshift(File.expand_path('..', __FILE__))
|
1
2
|
require 'amqp'
|
2
3
|
require 'mq'
|
3
4
|
require 'bunny'
|
@@ -17,6 +18,8 @@ module Beetle
|
|
17
18
|
class UnknownQueue < Error; end
|
18
19
|
# raised when no redis master server can be found
|
19
20
|
class NoRedisMaster < Error; end
|
21
|
+
# raise when no message could be sent by the publisher
|
22
|
+
class NoMessageSent < Error; end
|
20
23
|
|
21
24
|
# AMQP options for exchange creation
|
22
25
|
EXCHANGE_CREATION_KEYS = [:auto_delete, :durable, :internal, :nowait, :passive]
|
@@ -56,3 +59,5 @@ module Beetle
|
|
56
59
|
|
57
60
|
Timer = RUBY_VERSION < "1.9" ? SystemTimer : Timeout
|
58
61
|
end
|
62
|
+
|
63
|
+
require 'ext/qrack/client'
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'qrack/client'
|
2
|
+
|
3
|
+
unless Qrack::Client.instance_methods.include?("socket_with_reliable_timeout")
|
4
|
+
module Qrack
|
5
|
+
class Client
|
6
|
+
# overwrite the timeout method so that SystemTimer is used
|
7
|
+
# instead the standard timeout.rb: http://ph7spot.com/musings/system-timer
|
8
|
+
delegate :timeout, :to => Beetle::Timer
|
9
|
+
|
10
|
+
def socket_with_reliable_timeout
|
11
|
+
socket_without_reliable_timeout
|
12
|
+
|
13
|
+
secs = Integer(CONNECT_TIMEOUT)
|
14
|
+
usecs = Integer((CONNECT_TIMEOUT - secs) * 1_000_000)
|
15
|
+
optval = [secs, usecs].pack("l_2")
|
16
|
+
|
17
|
+
begin
|
18
|
+
@socket.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
|
19
|
+
@socket.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval
|
20
|
+
rescue Errno::ENOPROTOOPT
|
21
|
+
end
|
22
|
+
@socket
|
23
|
+
end
|
24
|
+
alias_method_chain :socket, :reliable_timeout
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/test/beetle/client_test.rb
CHANGED
@@ -11,6 +11,10 @@ module Beetle
|
|
11
11
|
assert_equal ["localhost:5672"], @client.servers
|
12
12
|
end
|
13
13
|
|
14
|
+
test "should have no additional subscription servers" do
|
15
|
+
assert_equal [], @client.additional_subscription_servers
|
16
|
+
end
|
17
|
+
|
14
18
|
test "should have no exchanges" do
|
15
19
|
assert @client.exchanges.empty?
|
16
20
|
end
|
@@ -12,7 +12,7 @@ module Beetle
|
|
12
12
|
config.config_file = "some/path/to/a/file"
|
13
13
|
assert_equal new_value, config.gc_threshold
|
14
14
|
end
|
15
|
-
|
15
|
+
|
16
16
|
test "should log to STDOUT if no log_file given" do
|
17
17
|
config = Configuration.new
|
18
18
|
Logger.expects(:new).with(STDOUT).returns(stub_everything)
|
@@ -27,4 +27,4 @@ module Beetle
|
|
27
27
|
config.logger
|
28
28
|
end
|
29
29
|
end
|
30
|
-
end
|
30
|
+
end
|
@@ -95,6 +95,8 @@ module Beetle
|
|
95
95
|
redis1.expects(:get).with("foo:x").raises("disconnected").in_sequence(s)
|
96
96
|
@store.expects(:redis).returns(redis2).in_sequence(s)
|
97
97
|
redis2.expects(:get).with("foo:x").returns("42").in_sequence(s)
|
98
|
+
@store.logger.expects(:info)
|
99
|
+
@store.logger.expects(:error)
|
98
100
|
assert_equal("42", @store.get("foo", "x"))
|
99
101
|
end
|
100
102
|
|
@@ -103,6 +105,8 @@ module Beetle
|
|
103
105
|
@store.stubs(:redis).returns(redis1)
|
104
106
|
redis1.stubs(:get).with("foo:x").raises("disconnected")
|
105
107
|
@store.stubs(:sleep)
|
108
|
+
@store.logger.stubs(:info)
|
109
|
+
@store.logger.stubs(:error)
|
106
110
|
assert_raises(NoRedisMaster) { @store.get("foo", "x") }
|
107
111
|
end
|
108
112
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
|
2
|
+
|
3
|
+
|
4
|
+
class QrackClientExtTest < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
Qrack::Client.any_instance.stubs(:create_channel).returns(nil)
|
7
|
+
@client = Qrack::Client.new
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
test "should use system-timer for reliable timeouts" do
|
12
|
+
Beetle::Timer.expects(:timeout)
|
13
|
+
@client.send :timeout, 1, 1 do
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
test "should set send/receive timeouts on the socket" do
|
18
|
+
socket_mock = mock("socket")
|
19
|
+
@client.instance_variable_set(:@socket, socket_mock)
|
20
|
+
@client.stubs(:socket_without_reliable_timeout)
|
21
|
+
|
22
|
+
socket_mock.expects(:setsockopt).with(Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, anything)
|
23
|
+
socket_mock.expects(:setsockopt).with(Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, anything)
|
24
|
+
@client.send(:socket)
|
25
|
+
end
|
26
|
+
end
|
data/test/beetle/message_test.rb
CHANGED
@@ -525,7 +525,7 @@ module Beetle
|
|
525
525
|
test "processing a message catches internal exceptions risen by process_internal and returns an internal error" do
|
526
526
|
header = header_with_params({})
|
527
527
|
message = Message.new("somequeue", header, 'foo', :store => @store)
|
528
|
-
message.expects(:process_internal).raises(Exception.new)
|
528
|
+
message.expects(:process_internal).raises(Exception.new("this is expected"))
|
529
529
|
handler = Handler.new
|
530
530
|
handler.expects(:process_exception).never
|
531
531
|
handler.expects(:process_failure).never
|
@@ -76,13 +76,16 @@ module Beetle
|
|
76
76
|
end
|
77
77
|
|
78
78
|
test "publishing should fail over to the next server" do
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
@pub.
|
83
|
-
|
84
|
-
|
85
|
-
|
79
|
+
@pub.servers << "localhost:3333"
|
80
|
+
raising_exchange = mock("raising exchange")
|
81
|
+
nice_exchange = mock("nice exchange")
|
82
|
+
@pub.stubs(:exchange).with("mama-exchange").returns(raising_exchange).then.returns(nice_exchange)
|
83
|
+
|
84
|
+
raising_exchange.expects(:publish).raises(Bunny::ConnectionError)
|
85
|
+
nice_exchange.expects(:publish)
|
86
|
+
@pub.expects(:set_current_server).twice
|
87
|
+
@pub.expects(:stop!).once
|
88
|
+
@pub.expects(:mark_server_dead).once
|
86
89
|
@pub.publish_with_failover("mama-exchange", "mama", @data, @opts)
|
87
90
|
end
|
88
91
|
|
@@ -114,7 +117,7 @@ module Beetle
|
|
114
117
|
assert_equal 1, @pub.publish_with_redundancy("mama-exchange", "mama", @data, @opts)
|
115
118
|
end
|
116
119
|
|
117
|
-
test "redundant publishing should
|
120
|
+
test "redundant publishing should raise an exception if the message was published to no server" do
|
118
121
|
redundant = sequence("redundant")
|
119
122
|
@pub.servers = ["someserver", "someotherserver"]
|
120
123
|
@pub.server = "someserver"
|
@@ -125,7 +128,9 @@ module Beetle
|
|
125
128
|
@pub.expects(:exchange).with("mama-exchange").returns(e).in_sequence(redundant)
|
126
129
|
e.expects(:publish).raises(Bunny::ConnectionError).in_sequence(redundant)
|
127
130
|
|
128
|
-
|
131
|
+
assert_raises Beetle::NoMessageSent do
|
132
|
+
@pub.publish_with_redundancy("mama-exchange", "mama", @data, @opts)
|
133
|
+
end
|
129
134
|
end
|
130
135
|
|
131
136
|
test "redundant publishing should fallback to failover publishing if less than one server is available" do
|
@@ -10,6 +10,12 @@ module Beetle
|
|
10
10
|
@client.stubs(:verify_redis_master_file_string)
|
11
11
|
end
|
12
12
|
|
13
|
+
test "should send a client_started message when started" do
|
14
|
+
@client.stubs(:clear_redis_master_file)
|
15
|
+
@client.beetle.expects(:publish).with(:client_started, {:id => @client.id}.to_json)
|
16
|
+
@client.start
|
17
|
+
end
|
18
|
+
|
13
19
|
test "config should return the beetle config" do
|
14
20
|
assert_equal Beetle.config, @client.config
|
15
21
|
end
|
@@ -59,13 +65,17 @@ module Beetle
|
|
59
65
|
|
60
66
|
test "should clear redis master file if redis from master file is slave" do
|
61
67
|
@client.stubs(:redis_master_from_master_file).returns(stub(:master? => false))
|
68
|
+
Beetle::Client.any_instance.stubs(:publish)
|
62
69
|
@client.expects(:clear_redis_master_file)
|
70
|
+
@client.expects(:client_started!)
|
63
71
|
@client.start
|
64
72
|
end
|
65
73
|
|
66
74
|
test "should clear redis master file if redis from master file is not available" do
|
67
75
|
@client.stubs(:redis_master_from_master_file).returns(nil)
|
76
|
+
Beetle::Client.any_instance.stubs(:publish)
|
68
77
|
@client.expects(:clear_redis_master_file)
|
78
|
+
@client.expects(:client_started!)
|
69
79
|
@client.start
|
70
80
|
end
|
71
81
|
|
@@ -287,10 +287,18 @@ module Beetle
|
|
287
287
|
|
288
288
|
test "should log and send a system notification when pong message from unknown client received" do
|
289
289
|
payload = {"id" => "unknown-client", "token" => @server.current_token}
|
290
|
-
msg = "Received pong message from unknown
|
290
|
+
msg = "Received pong message from unknown id 'unknown-client'"
|
291
291
|
@server.beetle.expects(:publish).with(:system_notification, ({:message => msg}).to_json)
|
292
292
|
@server.logger.expects(:error).with(msg)
|
293
293
|
@server.pong(payload)
|
294
294
|
end
|
295
|
+
|
296
|
+
test "should warn about unknown clients when receiving client_started messages" do
|
297
|
+
payload = {"id" => "unknown-client"}
|
298
|
+
msg = "Received client_started message from unknown id 'unknown-client'"
|
299
|
+
@server.beetle.expects(:publish).with(:system_notification, ({:message => msg}).to_json)
|
300
|
+
@server.logger.expects(:error).with(msg)
|
301
|
+
@server.client_started(payload)
|
302
|
+
end
|
295
303
|
end
|
296
304
|
end
|
@@ -51,6 +51,19 @@ module Beetle
|
|
51
51
|
|
52
52
|
end
|
53
53
|
|
54
|
+
class AdditionalSubscriptionServersTest < Test::Unit::TestCase
|
55
|
+
def setup
|
56
|
+
@config = Configuration.new
|
57
|
+
@config.additional_subscription_servers = "localhost:1234"
|
58
|
+
@client = Client.new(@config)
|
59
|
+
@sub = @client.send(:subscriber)
|
60
|
+
end
|
61
|
+
|
62
|
+
test "subscribers server list should contain addtional subcription hosts" do
|
63
|
+
assert_equal ["localhost:5672", "localhost:1234"], @sub.servers
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
54
67
|
class SubscriberQueueManagementTest < Test::Unit::TestCase
|
55
68
|
def setup
|
56
69
|
@client = Client.new
|
@@ -143,7 +156,7 @@ module Beetle
|
|
143
156
|
|
144
157
|
test "exceptions raised from message processing should be ignored" do
|
145
158
|
header = header_with_params({})
|
146
|
-
Message.any_instance.expects(:process).raises(Exception.new)
|
159
|
+
Message.any_instance.expects(:process).raises(Exception.new("don't worry"))
|
147
160
|
assert_nothing_raised { @callback.call(header, 'foo') }
|
148
161
|
end
|
149
162
|
|
@@ -214,12 +227,12 @@ module Beetle
|
|
214
227
|
proc = lambda do |m|
|
215
228
|
block_called = true
|
216
229
|
assert_equal header, m.header
|
217
|
-
assert_equal "
|
230
|
+
assert_equal "foo", m.data
|
218
231
|
assert_equal server, m.server
|
219
232
|
end
|
220
233
|
@sub.register_handler("some_queue", &proc)
|
221
234
|
q = mock("QUEUE")
|
222
|
-
q.expects(:subscribe).with({:ack => true, :key => "#"}).yields(header,
|
235
|
+
q.expects(:subscribe).with({:ack => true, :key => "#"}).yields(header, "foo")
|
223
236
|
@sub.expects(:queues).returns({"some_queue" => q})
|
224
237
|
@sub.send(:subscribe, "some_queue")
|
225
238
|
assert block_called
|
data/test/test_helper.rb
CHANGED
@@ -3,9 +3,20 @@ require 'active_support'
|
|
3
3
|
require 'active_support/testing/declarative'
|
4
4
|
require 'test/unit'
|
5
5
|
begin
|
6
|
-
require 'redgreen' unless ENV['TM_FILENAME']
|
6
|
+
require 'redgreen' unless ENV['TM_FILENAME']
|
7
7
|
rescue MissingSourceFile
|
8
8
|
end
|
9
|
+
|
10
|
+
# we can remove this hack which is needed only for testing
|
11
|
+
begin
|
12
|
+
require 'qrack/errors'
|
13
|
+
rescue LoadError
|
14
|
+
module Qrack
|
15
|
+
class BufferOverflowError < StandardError; end
|
16
|
+
class InvalidTypeError < StandardError; end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
9
20
|
require 'mocha'
|
10
21
|
require File.expand_path(File.dirname(__FILE__) + '/../lib/beetle')
|
11
22
|
|
metadata
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: beetle
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 121
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 2
|
9
|
-
-
|
10
|
-
|
9
|
+
- 9
|
10
|
+
- 1
|
11
|
+
version: 0.2.9.1
|
11
12
|
platform: ruby
|
12
13
|
authors:
|
13
14
|
- Stefan Kaes
|
@@ -18,7 +19,7 @@ autorequire:
|
|
18
19
|
bindir: bin
|
19
20
|
cert_chain: []
|
20
21
|
|
21
|
-
date: 2010-
|
22
|
+
date: 2010-11-15 00:00:00 +01:00
|
22
23
|
default_executable: beetle
|
23
24
|
dependencies:
|
24
25
|
- !ruby/object:Gem::Dependency
|
@@ -59,7 +60,7 @@ dependencies:
|
|
59
60
|
requirement: &id003 !ruby/object:Gem::Requirement
|
60
61
|
none: false
|
61
62
|
requirements:
|
62
|
-
- - "
|
63
|
+
- - "="
|
63
64
|
- !ruby/object:Gem::Version
|
64
65
|
hash: 7
|
65
66
|
segments:
|
@@ -91,7 +92,7 @@ dependencies:
|
|
91
92
|
requirement: &id005 !ruby/object:Gem::Requirement
|
92
93
|
none: false
|
93
94
|
requirements:
|
94
|
-
- -
|
95
|
+
- - ~>
|
95
96
|
- !ruby/object:Gem::Version
|
96
97
|
hash: 11
|
97
98
|
segments:
|
@@ -217,6 +218,7 @@ files:
|
|
217
218
|
- lib/beetle/redis_server_info.rb
|
218
219
|
- lib/beetle/subscriber.rb
|
219
220
|
- lib/beetle.rb
|
221
|
+
- lib/ext/qrack/client.rb
|
220
222
|
- features/README.rdoc
|
221
223
|
- features/redis_auto_failover.feature
|
222
224
|
- features/step_definitions/redis_auto_failover_steps.rb
|
@@ -240,6 +242,7 @@ files:
|
|
240
242
|
- test/beetle/client_test.rb
|
241
243
|
- test/beetle/configuration_test.rb
|
242
244
|
- test/beetle/deduplication_store_test.rb
|
245
|
+
- test/beetle/ext_test.rb
|
243
246
|
- test/beetle/handler_test.rb
|
244
247
|
- test/beetle/message_test.rb
|
245
248
|
- test/beetle/publisher_test.rb
|
@@ -293,6 +296,7 @@ test_files:
|
|
293
296
|
- test/beetle/client_test.rb
|
294
297
|
- test/beetle/configuration_test.rb
|
295
298
|
- test/beetle/deduplication_store_test.rb
|
299
|
+
- test/beetle/ext_test.rb
|
296
300
|
- test/beetle/handler_test.rb
|
297
301
|
- test/beetle/message_test.rb
|
298
302
|
- test/beetle/publisher_test.rb
|