beetle 3.5.1 → 3.5.5
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.
- checksums.yaml +4 -4
- data/Rakefile +9 -13
- data/beetle.gemspec +9 -4
- data/examples/redundant.rb +1 -1
- data/examples/rpc.rb +1 -1
- data/features/redis_auto_failover.feature +20 -0
- data/features/step_definitions/redis_auto_failover_steps.rb +35 -11
- data/features/support/test_daemons/redis_configuration_server.rb +2 -1
- data/lib/beetle/configuration.rb +20 -0
- data/lib/beetle/deduplication_store.rb +4 -4
- data/lib/beetle/message.rb +6 -6
- data/lib/beetle/publisher.rb +3 -1
- data/lib/beetle/redis_ext.rb +14 -0
- data/lib/beetle/version.rb +1 -1
- data/lib/beetle.rb +9 -8
- data/test/beetle/amqp_gem_behavior_test.rb +1 -1
- data/test/beetle/base_test.rb +1 -1
- data/test/beetle/beetle_test.rb +7 -4
- data/test/beetle/client_test.rb +7 -3
- data/test/beetle/deduplication_store_test.rb +1 -1
- data/test/beetle/publisher_test.rb +15 -6
- data/test/beetle/redis_ext_test.rb +5 -1
- data/test/beetle/subscriber_test.rb +8 -4
- data/test/test_helper.rb +16 -9
- metadata +97 -35
- data/README.rdoc +0 -233
- data/REDIS_AUTO_FAILOVER.rdoc +0 -116
- data/RELEASE_NOTES.rdoc +0 -383
- data/examples/README.rdoc +0 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a0e823ee91e804e241eefc9057879a94c538c43c45f04af9c0e9491d6bc9e0b3
|
4
|
+
data.tar.gz: b211a4cfb395af0a048431fa53f865a0b9b9a61478d5f0527cf8c794a4751273
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 244515a2a89649e6f09900b910bbe29c4d624b1750051136d26e7f49fcf825910b602f6714438e8a3a8bd9f9822af2a0955afbfe82cd46cb23acd393999346f8
|
7
|
+
data.tar.gz: 3735d575d65abbf415e9bad4c74c2c84720144cd3147c6687061413eeaf296ea46a739bfdb731c62c4031bf751869acf4ef8ccc269146067c36a354f69fcb08c
|
data/Rakefile
CHANGED
@@ -98,7 +98,7 @@ namespace :consul do
|
|
98
98
|
end
|
99
99
|
|
100
100
|
Cucumber::Rake::Task.new(:cucumber) do |t|
|
101
|
-
t.cucumber_opts = "features --format progress"
|
101
|
+
t.cucumber_opts = ["features", "--format progress"]
|
102
102
|
end
|
103
103
|
|
104
104
|
task :cucumber => :clean
|
@@ -115,18 +115,14 @@ Rake::TestTask.new do |t|
|
|
115
115
|
t.warning = false
|
116
116
|
end
|
117
117
|
|
118
|
-
|
119
|
-
|
120
|
-
RDoc::Task.new do |rdoc|
|
121
|
-
rdoc.rdoc_dir = 'site/rdoc'
|
122
|
-
rdoc.title = 'Beetle'
|
123
|
-
rdoc.main = 'README.rdoc'
|
124
|
-
rdoc.options << '--line-numbers' << '--inline-source' << '--quiet'
|
125
|
-
rdoc.rdoc_files.include('**/*.rdoc')
|
126
|
-
rdoc.rdoc_files.include('MIT-LICENSE')
|
127
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
118
|
+
task :clean do
|
119
|
+
sh "rm -f tmp/*.output tmp/*.log tmp/master-dir/* tmp/slave-dir/* tmp/*lock tmp/*pid test.log" unless ENV['GITHUB_ACTIONS']
|
128
120
|
end
|
129
121
|
|
130
|
-
|
131
|
-
|
122
|
+
require 'yard'
|
123
|
+
|
124
|
+
YARD::Rake::YardocTask.new do |t|
|
125
|
+
OTHER_PATHS = %w()
|
126
|
+
t.files = ['lib/**/*.rb'] + OTHER_PATHS
|
127
|
+
t.options = %w(--markup-provider=redcarpet --markup=markdown --main=README.md --output-dir=site/yard)
|
132
128
|
end
|
data/beetle.gemspec
CHANGED
@@ -31,16 +31,21 @@ Gem::Specification.new do |s|
|
|
31
31
|
s.add_runtime_dependency "activesupport", ">= 2.3.4"
|
32
32
|
|
33
33
|
s.add_development_dependency "activerecord", "~> 5.0"
|
34
|
-
s.add_development_dependency "cucumber", "~>
|
34
|
+
s.add_development_dependency "cucumber", "~> 8.0.0"
|
35
35
|
s.add_development_dependency "daemon_controller", "~> 1.2.0"
|
36
36
|
s.add_development_dependency "daemons", ">= 1.2.0"
|
37
37
|
s.add_development_dependency "i18n"
|
38
38
|
s.add_development_dependency "minitest", "~> 5.1"
|
39
|
-
s.add_development_dependency "
|
40
|
-
s.add_development_dependency "
|
39
|
+
s.add_development_dependency "minitest-reporters"
|
40
|
+
s.add_development_dependency "mocha", "~> 1.14"
|
41
|
+
s.add_development_dependency "mysql2", "~> 0.5"
|
41
42
|
s.add_development_dependency "rake", "~> 13.0"
|
42
|
-
s.add_development_dependency "rdoc", "~> 4.0"
|
43
43
|
s.add_development_dependency "simplecov", "~> 0.15"
|
44
44
|
s.add_development_dependency "webmock", "~> 3.0"
|
45
45
|
s.add_development_dependency "websocket-eventmachine-client"
|
46
|
+
s.add_development_dependency 'yard'
|
47
|
+
s.add_development_dependency 'redcarpet'
|
48
|
+
s.add_development_dependency 'github-markup'
|
49
|
+
s.add_development_dependency 'byebug'
|
50
|
+
s.add_development_dependency 'appraisal'
|
46
51
|
end
|
data/examples/redundant.rb
CHANGED
@@ -17,7 +17,7 @@ Beetle.config.logger.level = Logger::INFO
|
|
17
17
|
client = Beetle::Client.new
|
18
18
|
|
19
19
|
# use two servers
|
20
|
-
Beetle.config.servers = "localhost:5672, localhost:5673"
|
20
|
+
Beetle.config.servers = ENV["RABBITMQ_SERVERS"] || "localhost:5672, localhost:5673"
|
21
21
|
# instantiate a client
|
22
22
|
client = Beetle::Client.new
|
23
23
|
|
data/examples/rpc.rb
CHANGED
@@ -4,7 +4,7 @@ require File.expand_path(File.dirname(__FILE__)+"/../lib/beetle")
|
|
4
4
|
|
5
5
|
# suppress debug messages
|
6
6
|
Beetle.config.logger.level = Logger::INFO
|
7
|
-
Beetle.config.servers = "localhost:5672, localhost:5673"
|
7
|
+
Beetle.config.servers = ENV["RABBITMQ_SERVERS"] || "localhost:5672, localhost:5673"
|
8
8
|
# instantiate a client
|
9
9
|
|
10
10
|
client = Beetle::Client.new
|
@@ -24,6 +24,26 @@ Feature: Redis auto failover
|
|
24
24
|
Given a redis server "redis-1" exists as master
|
25
25
|
Then the role of redis server "redis-1" should be "slave"
|
26
26
|
|
27
|
+
Scenario: Successful redis master switch with multiple slaves
|
28
|
+
And a redis server "redis-3" exists as slave of "redis-3"
|
29
|
+
Given a redis configuration server using redis servers "redis-1,redis-2,redis-3" with clients "rc-client-1,rc-client-2" exists
|
30
|
+
And a redis configuration client "rc-client-1" using redis servers "redis-1,redis-2,redis-3" exists
|
31
|
+
And a redis configuration client "rc-client-2" using redis servers "redis-1,redis-2,redis-3" exists
|
32
|
+
And a beetle handler using the redis-master file from "rc-client-1" exists
|
33
|
+
And redis server "redis-1" is down
|
34
|
+
And the retry timeout for the redis master check is reached
|
35
|
+
Then a system notification for "redis-1" not being available should be sent
|
36
|
+
And the role of redis server "redis-2" should be "master"
|
37
|
+
And the redis master file of the redis configuration server should contain "redis-2"
|
38
|
+
And the redis master of "rc-client-1" should be "redis-2"
|
39
|
+
And the redis master of "rc-client-2" should be "redis-2"
|
40
|
+
And the redis master of the beetle handler should be "redis-2"
|
41
|
+
And a system notification for switching from "redis-1" to "redis-2" should be sent
|
42
|
+
Given a redis server "redis-1" exists as master
|
43
|
+
Then the role of redis server "redis-1" should be "slave"
|
44
|
+
And the redis server "redis-1" is a slave of "redis-2"
|
45
|
+
And the redis server "redis-3" is a slave of "redis-2"
|
46
|
+
|
27
47
|
Scenario: Successful single redis master switch with multiple failover sets
|
28
48
|
Given a redis server "redis-3" exists as master
|
29
49
|
And a redis server "redis-4" exists as slave of "redis-3"
|
@@ -1,8 +1,9 @@
|
|
1
1
|
Given /^consul state has been cleared$/ do
|
2
|
+
consul_host = ENV["CONSUL_HOST"] || "localhost:8500"
|
2
3
|
system "killall beetle beetle_handler >/dev/null 2>/dev/null"
|
3
|
-
system "curl --silent --request PUT http
|
4
|
-
system "curl --silent --request PUT http
|
5
|
-
system "curl --silent --request DELETE http
|
4
|
+
system "curl --silent --request PUT http://#{consul_host}/v1/kv/apps/beetle/config/ >/dev/null"
|
5
|
+
system "curl --silent --request PUT http://#{consul_host}/v1/kv/shared/config/ >/dev/null"
|
6
|
+
system "curl --silent --request DELETE http://#{consul_host}/v1/kv/apps/beetle/state/redis_master_file_content >/dev/null"
|
6
7
|
end
|
7
8
|
|
8
9
|
Given /^a redis server "([^\"]*)" exists as master$/ do |redis_name|
|
@@ -93,7 +94,6 @@ Given /^an old redis master file for "([^\"]*)" with master "([^\"]*)" exists$/
|
|
93
94
|
end
|
94
95
|
end
|
95
96
|
|
96
|
-
|
97
97
|
Then /^the role of redis server "([^\"]*)" should be "(master|slave)"$/ do |redis_name, role|
|
98
98
|
expected_role = false
|
99
99
|
10.times do
|
@@ -103,6 +103,16 @@ Then /^the role of redis server "([^\"]*)" should be "(master|slave)"$/ do |redi
|
|
103
103
|
assert expected_role, "#{redis_name} is not a #{role}"
|
104
104
|
end
|
105
105
|
|
106
|
+
Then /^the redis server "([^\"]*)" is a slave of "([^\"]*)"$/ do |redis_name, redis_master_name|
|
107
|
+
master = TestDaemons::Redis[redis_master_name].redis
|
108
|
+
slave = TestDaemons::Redis[redis_name].redis
|
109
|
+
3.times do
|
110
|
+
sleep 1
|
111
|
+
break if slave.slave_of?(master.host, master.port)
|
112
|
+
end
|
113
|
+
assert slave.slave_of?(master.host, master.port)
|
114
|
+
end
|
115
|
+
|
106
116
|
Then /^the redis master of "([^\"]*)" (?:in system "([^"]*)" )?should be "([^\"]*)"$/ do |redis_configuration_client_name, system_name, redis_name|
|
107
117
|
system_name ||= "system"
|
108
118
|
master_file = redis_master_file(redis_configuration_client_name)
|
@@ -143,11 +153,21 @@ end
|
|
143
153
|
Then /^the redis master of the beetle handler should be "([^\"]*)"$/ do |redis_name|
|
144
154
|
Beetle.config.servers = "127.0.0.1:5672" # rabbitmq
|
145
155
|
Beetle.config.logger.level = Logger::INFO
|
146
|
-
|
147
|
-
|
148
|
-
|
156
|
+
redis_master = TestDaemons::Redis[redis_name].ip_with_port
|
157
|
+
response = nil
|
158
|
+
expected_response = ['OK', redis_master]
|
159
|
+
3.times do |i|
|
160
|
+
client = Beetle::Client.new.configure :auto_delete => true do |config|
|
161
|
+
config.queue(:echo, :lazy => true, :dead_lettering => true)
|
162
|
+
config.message(:echo)
|
163
|
+
end
|
164
|
+
t1 = Time.now
|
165
|
+
response = client.rpc(:echo, 'echo')
|
166
|
+
t2 = Time.now
|
167
|
+
# puts "OK,#{redis_master} =?= #{response.join(',')} after #{t2-t1}, attempt #{i+1}"
|
168
|
+
break if expected_response == response
|
149
169
|
end
|
150
|
-
|
170
|
+
assert_equal expected_response, response
|
151
171
|
end
|
152
172
|
|
153
173
|
Then /^a system notification for "([^\"]*)" not being available should be sent$/ do |redis_name|
|
@@ -166,8 +186,12 @@ end
|
|
166
186
|
|
167
187
|
Then /^a system notification for no slave available to become new master should be sent$/ do
|
168
188
|
text = "Redis master could not be switched, no slave available to become new master"
|
169
|
-
|
170
|
-
|
189
|
+
tail = ""
|
190
|
+
3.times do
|
191
|
+
lines = File.readlines(system_notification_log_path)
|
192
|
+
tail = (["","",""]+lines)[-3..-1].join("\n")
|
193
|
+
sleep 0.1 unless tail =~ /#{text}/
|
194
|
+
end
|
171
195
|
assert_match /#{text}/, tail
|
172
196
|
end
|
173
197
|
|
@@ -179,7 +203,7 @@ end
|
|
179
203
|
|
180
204
|
Given /^an immediate master switch is initiated and responds with (\d+)$/ do |response_code|
|
181
205
|
response = TestDaemons::RedisConfigurationServer.initiate_master_switch
|
182
|
-
assert_equal response_code, response.code, "unexpected response code #{response.code}, message: #{response.body}"
|
206
|
+
assert_equal response_code.to_s, response.code, "unexpected response code #{response.code}, message: #{response.body}"
|
183
207
|
sleep 1
|
184
208
|
end
|
185
209
|
|
@@ -25,9 +25,10 @@ module TestDaemons
|
|
25
25
|
|
26
26
|
def self.daemon_controller
|
27
27
|
clients_parameter_string = @@redis_configuration_clients.blank? ? "" : "--client-ids #{@@redis_configuration_clients}"
|
28
|
+
consul_host = ENV["CONSUL_HOST"] || "localhost:8500"
|
28
29
|
DaemonController.new(
|
29
30
|
:identifier => "Redis configuration test server",
|
30
|
-
:start_command => "./beetle configuration_server -v -d --redis-master-file #{redis_master_file} --redis-servers '#{@@redis_servers}' #{clients_parameter_string} --redis-master-retry-interval 1 --pid-file #{pid_file} --log-file #{log_file} --redis-failover-confidence-level #{@@confidence_level} --consul http
|
31
|
+
:start_command => "./beetle configuration_server -v -d --redis-master-file #{redis_master_file} --redis-servers '#{@@redis_servers}' #{clients_parameter_string} --redis-master-retry-interval 1 --pid-file #{pid_file} --log-file #{log_file} --redis-failover-confidence-level #{@@confidence_level} --consul http://#{consul_host}",
|
31
32
|
:ping_command => lambda{ answers_text_requests? },
|
32
33
|
:pid_file => pid_file,
|
33
34
|
:log_file => log_file,
|
data/lib/beetle/configuration.rb
CHANGED
@@ -33,6 +33,12 @@ module Beetle
|
|
33
33
|
attr_accessor :redis_servers
|
34
34
|
# redis database number to use for the message deduplication store (defaults to <tt>4</tt>)
|
35
35
|
attr_accessor :redis_db
|
36
|
+
# redis connect timeout. defaults to 5 seconds.
|
37
|
+
attr_accessor :redis_connect_timeout
|
38
|
+
# redis read timeout. defaults to 5 seconds.
|
39
|
+
attr_accessor :redis_read_timeout
|
40
|
+
# redis write timeout. defaults to 5 seconds.
|
41
|
+
attr_accessor :redis_write_timeout
|
36
42
|
|
37
43
|
# how long we should repeatedly retry a redis operation before giving up, with a one
|
38
44
|
# second sleep between retries (defaults to <tt>180.seconds</tt>). this value needs to be
|
@@ -157,6 +163,9 @@ module Beetle
|
|
157
163
|
self.redis_server = "localhost:6379"
|
158
164
|
self.redis_servers = ""
|
159
165
|
self.redis_db = 4
|
166
|
+
self.redis_connect_timeout = 5.0
|
167
|
+
self.redis_read_timeout = 5.0
|
168
|
+
self.redis_write_timeout = 5.0
|
160
169
|
self.redis_failover_timeout = 180.seconds
|
161
170
|
self.redis_status_key_expiry_interval = 0.seconds
|
162
171
|
self.redis_failover_client_heartbeat_interval = 10.seconds
|
@@ -217,6 +226,17 @@ module Beetle
|
|
217
226
|
end
|
218
227
|
end
|
219
228
|
|
229
|
+
# redis optins to be passed to Redis.new
|
230
|
+
def redis_options
|
231
|
+
{
|
232
|
+
db: redis_db,
|
233
|
+
connect_timeout: redis_connect_timeout,
|
234
|
+
read_timeout: redis_read_timeout,
|
235
|
+
write_timeout: redis_write_timeout,
|
236
|
+
logger: redis_logger,
|
237
|
+
}
|
238
|
+
end
|
239
|
+
|
220
240
|
private
|
221
241
|
def load_config
|
222
242
|
raw = ERB.new(IO.read(config_file)).result
|
@@ -62,7 +62,7 @@ module Beetle
|
|
62
62
|
# store completion status for given <tt>msg_id</tt> if it doesn't exist yet. Returns whether the
|
63
63
|
# operation was successful.
|
64
64
|
def setnx_completed!(msg_id)
|
65
|
-
expiry = @config.redis_status_key_expiry_interval
|
65
|
+
expiry = @config.redis_status_key_expiry_interval.to_i
|
66
66
|
return true if expiry == 0
|
67
67
|
with_failover do
|
68
68
|
redis.set(
|
@@ -109,7 +109,7 @@ module Beetle
|
|
109
109
|
|
110
110
|
# delete all keys associated with the given <tt>msg_id</tt>.
|
111
111
|
def del_keys(msg_id)
|
112
|
-
expiry = @config.redis_status_key_expiry_interval
|
112
|
+
expiry = @config.redis_status_key_expiry_interval.to_i
|
113
113
|
keys = keys(msg_id)
|
114
114
|
status_key = keys.shift if expiry > 0
|
115
115
|
with_failover do
|
@@ -150,7 +150,7 @@ module Beetle
|
|
150
150
|
|
151
151
|
# set current redis master instance (as specified in the Beetle::Configuration)
|
152
152
|
def redis_master_from_server_string
|
153
|
-
@current_master ||= Redis.from_server_string(@config.redis_server,
|
153
|
+
@current_master ||= Redis.from_server_string(@config.redis_server, @config.redis_options)
|
154
154
|
end
|
155
155
|
|
156
156
|
# set current redis master from master file
|
@@ -168,7 +168,7 @@ module Beetle
|
|
168
168
|
def set_current_redis_master_from_master_file
|
169
169
|
@last_time_master_file_changed = File.mtime(@config.redis_server)
|
170
170
|
server_string = extract_redis_master(read_master_file)
|
171
|
-
@current_master = !server_string.blank? ? Redis.from_server_string(server_string,
|
171
|
+
@current_master = !server_string.blank? ? Redis.from_server_string(server_string, @config.redis_options ) : nil
|
172
172
|
end
|
173
173
|
|
174
174
|
# extract redis master from file content and return the server for our system
|
data/lib/beetle/message.rb
CHANGED
@@ -72,15 +72,15 @@ module Beetle
|
|
72
72
|
|
73
73
|
def setup(opts) #:nodoc:
|
74
74
|
@server = opts[:server]
|
75
|
-
@timeout = opts[:timeout] || DEFAULT_HANDLER_TIMEOUT
|
76
|
-
@delay = opts[:delay]
|
75
|
+
@timeout = opts[:timeout] || DEFAULT_HANDLER_TIMEOUT.to_i
|
76
|
+
@delay = (opts[:delay] || DEFAULT_HANDLER_EXECUTION_ATTEMPTS_DELAY).ceil
|
77
77
|
@attempts_limit = opts[:attempts] || DEFAULT_HANDLER_EXECUTION_ATTEMPTS
|
78
78
|
@exceptions_limit = opts[:exceptions] || DEFAULT_EXCEPTION_LIMIT
|
79
79
|
@attempts_limit = @exceptions_limit + 1 if @attempts_limit <= @exceptions_limit
|
80
80
|
@retry_on = opts[:retry_on] || nil
|
81
81
|
@store = opts[:store]
|
82
82
|
max_delay = opts[:max_delay] || @delay
|
83
|
-
@max_delay = max_delay if max_delay >= 2*@delay
|
83
|
+
@max_delay = max_delay.ceil if max_delay >= 2*@delay
|
84
84
|
end
|
85
85
|
|
86
86
|
# extracts various values from the AMQP header properties
|
@@ -101,7 +101,7 @@ module Beetle
|
|
101
101
|
def self.publishing_options(opts = {}) #:nodoc:
|
102
102
|
flags = 0
|
103
103
|
flags |= FLAG_REDUNDANT if opts[:redundant]
|
104
|
-
expires_at = now + (opts[:ttl] || DEFAULT_TTL)
|
104
|
+
expires_at = now + (opts[:ttl] || DEFAULT_TTL).to_i
|
105
105
|
opts = opts.slice(*PUBLISHING_KEYS)
|
106
106
|
opts[:message_id] = generate_uuid.to_s
|
107
107
|
opts[:timestamp] = now
|
@@ -165,7 +165,7 @@ module Beetle
|
|
165
165
|
|
166
166
|
# store handler timeout timestamp in the deduplication store
|
167
167
|
def set_timeout!
|
168
|
-
@store.set(msg_id, :timeout, now + timeout)
|
168
|
+
@store.set(msg_id, :timeout, (now + timeout).ceil)
|
169
169
|
end
|
170
170
|
|
171
171
|
# handler timed out?
|
@@ -230,7 +230,7 @@ module Beetle
|
|
230
230
|
# have we already seen this message? if not, set the status to "incomplete" and store
|
231
231
|
# the message exipration timestamp in the deduplication store.
|
232
232
|
def key_exists?
|
233
|
-
old_message = !@store.msetnx(msg_id, :status =>"incomplete", :expires => @expires_at, :timeout => now + timeout)
|
233
|
+
old_message = !@store.msetnx(msg_id, :status =>"incomplete", :expires => @expires_at.to_i, :timeout => (now + timeout).to_i)
|
234
234
|
if old_message
|
235
235
|
logger.debug "Beetle: received duplicate message: #{msg_id} on queue: #{@queue}"
|
236
236
|
end
|
data/lib/beetle/publisher.rb
CHANGED
@@ -34,7 +34,7 @@ module Beetle
|
|
34
34
|
Bunny::ConnectionError, Bunny::ForcedChannelCloseError, Bunny::ForcedConnectionCloseError,
|
35
35
|
Bunny::MessageError, Bunny::ProtocolError, Bunny::ServerDownError, Bunny::UnsubscribeError,
|
36
36
|
Bunny::AcknowledgementError, Qrack::BufferOverflowError, Qrack::InvalidTypeError,
|
37
|
-
Errno::EHOSTUNREACH, Errno::ECONNRESET, Timeout::Error
|
37
|
+
Errno::EHOSTUNREACH, Errno::ECONNRESET, Errno::ETIMEDOUT, Timeout::Error
|
38
38
|
]
|
39
39
|
end
|
40
40
|
|
@@ -66,6 +66,7 @@ module Beetle
|
|
66
66
|
logger.debug "Beetle: message sent!"
|
67
67
|
published = 1
|
68
68
|
rescue *bunny_exceptions => e
|
69
|
+
logger.warn("Beetle: publishing exception #{e} #{e.backtrace[0..4].join("\n")}")
|
69
70
|
stop!(e)
|
70
71
|
tries -= 1
|
71
72
|
# retry same server on receiving the first exception for it (might have been a normal restart)
|
@@ -98,6 +99,7 @@ module Beetle
|
|
98
99
|
published << @server
|
99
100
|
logger.debug "Beetle: message sent (#{published})!"
|
100
101
|
rescue *bunny_exceptions => e
|
102
|
+
logger.warn("Beetle: publishing exception #{e} #{e.backtrace[0..4].join("\n")}")
|
101
103
|
stop!(e)
|
102
104
|
retry if (tries += 1) == 1
|
103
105
|
mark_server_dead
|
data/lib/beetle/redis_ext.rb
CHANGED
@@ -3,6 +3,7 @@ class Redis #:nodoc:
|
|
3
3
|
def self.from_server_string(server_string, options = {})
|
4
4
|
host, port = server_string.split(':')
|
5
5
|
options = {:host => host, :port => port}.update(options)
|
6
|
+
options.delete(:logger) if Redis::VERSION >= "5.0"
|
6
7
|
new(options)
|
7
8
|
end
|
8
9
|
|
@@ -79,6 +80,19 @@ class Redis #:nodoc:
|
|
79
80
|
super != 0
|
80
81
|
end
|
81
82
|
|
83
|
+
elsif Redis::VERSION >= "5.0.0"
|
84
|
+
|
85
|
+
# redis 5.0.0 has a shutdown method which raises if a connection to the redis server
|
86
|
+
# cannot be established.
|
87
|
+
module SaneShutdown
|
88
|
+
def shutdown
|
89
|
+
super
|
90
|
+
rescue RedisClient::CannotConnectError
|
91
|
+
nil
|
92
|
+
end
|
93
|
+
end
|
94
|
+
prepend SaneShutdown
|
95
|
+
|
82
96
|
elsif Redis::VERSION >= "4.0.0"
|
83
97
|
|
84
98
|
# redis 4.0.0 has a shutdown method which raises if a connection to the redis server
|
data/lib/beetle/version.rb
CHANGED
data/lib/beetle.rb
CHANGED
@@ -1,8 +1,13 @@
|
|
1
1
|
$:.unshift(File.expand_path('..', __FILE__))
|
2
2
|
require 'bunny' # which bunny picks up
|
3
3
|
require 'qrack/errors' # needed by the publisher
|
4
|
-
|
5
|
-
require 'redis'
|
4
|
+
begin
|
5
|
+
require 'redis/connection/hiredis' # require *before* redis as specified in the redis-rb gem docs
|
6
|
+
require 'redis'
|
7
|
+
rescue LoadError
|
8
|
+
require 'redis'
|
9
|
+
require 'hiredis-client'
|
10
|
+
end
|
6
11
|
require 'active_support/all'
|
7
12
|
require 'set'
|
8
13
|
require 'socket'
|
@@ -39,12 +44,8 @@ module Beetle
|
|
39
44
|
# determine the fully qualified domainname of the host we're running on
|
40
45
|
def self.hostname
|
41
46
|
name = Socket.gethostname
|
42
|
-
|
43
|
-
|
44
|
-
name
|
45
|
-
else
|
46
|
-
Socket.gethostbyname(parts.first).first rescue name
|
47
|
-
end
|
47
|
+
host = name.split('.').first
|
48
|
+
Addrinfo.getaddrinfo(host, nil, nil, :STREAM, nil, Socket::AI_CANONNAME).first.canonname rescue name
|
48
49
|
end
|
49
50
|
|
50
51
|
# use ruby's autoload mechanism for loading beetle classes
|
@@ -7,7 +7,7 @@ class AMQPGemBehaviorTest < Minitest::Test
|
|
7
7
|
begin
|
8
8
|
exception = nil
|
9
9
|
EM.run do
|
10
|
-
AMQP.start(:
|
10
|
+
AMQP.start(logging: false, host: ENV['RABBITMQ_SERVERS'] || 'localhost') do |connection|
|
11
11
|
EM::Timer.new(1){ connection.close { EM.stop }}
|
12
12
|
channel = AMQP::Channel.new(connection)
|
13
13
|
channel.on_error { puts "woot"}
|
data/test/beetle/base_test.rb
CHANGED
data/test/beetle/beetle_test.rb
CHANGED
@@ -2,15 +2,18 @@ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
|
|
2
2
|
|
3
3
|
module Beetle
|
4
4
|
class HostnameTest < Minitest::Test
|
5
|
-
|
5
|
+
test "should use canonical name if possible " do
|
6
|
+
addr = mock("addr")
|
7
|
+
addr.expects(:canonname).returns("a.b.com")
|
6
8
|
Socket.expects(:gethostname).returns("a.b.com")
|
9
|
+
Addrinfo.expects(:getaddrinfo).with("a", nil, nil, :STREAM, nil, Socket::AI_CANONNAME).returns([addr])
|
7
10
|
assert_equal "a.b.com", Beetle.hostname
|
8
11
|
end
|
9
12
|
|
10
|
-
test "should use Socket.
|
13
|
+
test "should use Socket.gethostbyname if Addrinfo raises" do
|
11
14
|
Socket.expects(:gethostname).returns("a")
|
12
|
-
|
13
|
-
assert_equal "a
|
15
|
+
Addrinfo.expects(:getaddrinfo).with("a", nil, nil, :STREAM, nil, Socket::AI_CANONNAME).raises("murks")
|
16
|
+
assert_equal "a", Beetle.hostname
|
14
17
|
end
|
15
18
|
end
|
16
19
|
end
|
data/test/beetle/client_test.rb
CHANGED
@@ -8,7 +8,7 @@ module Beetle
|
|
8
8
|
end
|
9
9
|
|
10
10
|
test "should have a default server" do
|
11
|
-
|
11
|
+
assert !@client.servers.empty?
|
12
12
|
end
|
13
13
|
|
14
14
|
test "should have no additional subscription servers" do
|
@@ -327,7 +327,9 @@ module Beetle
|
|
327
327
|
client.register_queue("b_queue")
|
328
328
|
client.register_queue("a_queue")
|
329
329
|
client.send(:subscriber).expects(:listen_queues).with {|value| value.include?("a_queue") && value.include?("b_queue")}.yields
|
330
|
-
|
330
|
+
called = false
|
331
|
+
client.listen { called = true }
|
332
|
+
assert called
|
331
333
|
end
|
332
334
|
|
333
335
|
test "trying to listen to a message is no longer supported and should raise an exception" do
|
@@ -338,7 +340,9 @@ module Beetle
|
|
338
340
|
client = Client.new
|
339
341
|
client.register_queue(:test)
|
340
342
|
client.send(:subscriber).expects(:listen_queues).with(['test']).yields
|
341
|
-
|
343
|
+
called = false
|
344
|
+
client.listen_queues([:test]) { called = true }
|
345
|
+
assert called
|
342
346
|
end
|
343
347
|
|
344
348
|
test "trying to listen to an unknown queue should raise an exception" do
|
@@ -87,7 +87,7 @@ module Beetle
|
|
87
87
|
private
|
88
88
|
def redis_test_master_file(server_string)
|
89
89
|
tmp_dir = File.expand_path("../../../tmp", __FILE__)
|
90
|
-
Dir.mkdir(tmp_dir) unless File.
|
90
|
+
Dir.mkdir(tmp_dir) unless File.exist?(tmp_dir)
|
91
91
|
path = tmp_dir + "/redis-master-for-unit-tests"
|
92
92
|
File.open(path, "w"){|f| f.puts server_string}
|
93
93
|
path
|
@@ -77,7 +77,9 @@ module Beetle
|
|
77
77
|
|
78
78
|
class PublisherPublishingTest < Minitest::Test
|
79
79
|
def setup
|
80
|
-
@
|
80
|
+
@config = Configuration.new
|
81
|
+
@config.servers = ENV['RABBITMQ_SERVERS'].split(',').first if ENV['RABBITMQ_SERVERS']
|
82
|
+
@client = Client.new(@config)
|
81
83
|
@pub = Publisher.new(@client)
|
82
84
|
@pub.stubs(:bind_queues_for_exchange)
|
83
85
|
@client.register_queue("mama", :exchange => "mama-exchange")
|
@@ -277,6 +279,7 @@ module Beetle
|
|
277
279
|
class PublisherQueueManagementTest < Minitest::Test
|
278
280
|
def setup
|
279
281
|
@config = Configuration.new
|
282
|
+
@config.servers = ENV['RABBITMQ_SERVERS'] if ENV['RABBITMQ_SERVERS']
|
280
283
|
@client = Client.new(@config)
|
281
284
|
@pub = Publisher.new(@client)
|
282
285
|
end
|
@@ -305,7 +308,9 @@ module Beetle
|
|
305
308
|
@client.register_queue('test_queue_2', :exchange => 'test_exchange')
|
306
309
|
@client.register_queue('test_queue_3', :exchange => 'test_exchange_2')
|
307
310
|
queue = mock("queue")
|
308
|
-
|
311
|
+
@pub.expects(:bind_queue!).with(queue, "test_exchange", {:key => "test_queue_1"}).once
|
312
|
+
@pub.expects(:bind_queue!).with(queue, "test_exchange", {:key => "test_queue_2"}).once
|
313
|
+
@pub.expects(:bind_queue!).with(queue, "test_exchange_2", {:key => "test_queue_3"}).once
|
309
314
|
@pub.expects(:declare_queue!).returns(queue).times(3)
|
310
315
|
@pub.send(:bind_queues_for_exchange, 'test_exchange')
|
311
316
|
@pub.send(:bind_queues_for_exchange, 'test_exchange_2')
|
@@ -315,18 +320,21 @@ module Beetle
|
|
315
320
|
@client.register_queue('test_queue_1', :exchange => 'test_exchange')
|
316
321
|
@client.register_queue('test_queue_2', :exchange => 'test_exchange')
|
317
322
|
queue = mock("queue")
|
318
|
-
queue.expects(:bind).twice
|
319
323
|
@pub.expects(:declare_queue!).returns(queue).twice
|
324
|
+
@pub.expects(:bind_queue!).with(queue, "test_exchange", {:key => "test_queue_1"}).once
|
325
|
+
@pub.expects(:bind_queue!).with(queue, "test_exchange", {:key => "test_queue_2"}).once
|
320
326
|
@pub.send(:bind_queues_for_exchange, 'test_exchange')
|
321
327
|
@pub.send(:bind_queues_for_exchange, 'test_exchange')
|
322
328
|
end
|
323
329
|
|
324
330
|
test "should declare queues only once even with many bindings" do
|
331
|
+
|
325
332
|
@client.register_queue('test_queue', :exchange => 'test_exchange')
|
326
333
|
@client.register_binding('test_queue', :exchange => 'test_exchange', :key => 'sir-message-a-lot')
|
327
334
|
queue = mock("queue")
|
328
|
-
queue.expects(:bind).twice
|
329
335
|
@pub.expects(:declare_queue!).returns(queue).once
|
336
|
+
@pub.expects(:bind_queue!).with(queue, "test_exchange", {:key => "test_queue"}).once
|
337
|
+
@pub.expects(:bind_queue!).with(queue, "test_exchange", {:key => "sir-message-a-lot"}).once
|
330
338
|
@pub.send(:bind_queues_for_exchange, 'test_exchange')
|
331
339
|
end
|
332
340
|
|
@@ -549,10 +557,11 @@ module Beetle
|
|
549
557
|
end
|
550
558
|
end
|
551
559
|
|
552
|
-
|
553
560
|
class RPCTest < Minitest::Test
|
554
561
|
def setup
|
555
|
-
@
|
562
|
+
@config = Configuration.new
|
563
|
+
@config.servers = ENV['RABBITMQ_SERVERS'].split(',').first if ENV['RABBITMQ_SERVERS']
|
564
|
+
@client = Client.new(@config)
|
556
565
|
@pub = Publisher.new(@client)
|
557
566
|
@client.register_message(:test, :exchange => :some_exchange)
|
558
567
|
end
|
@@ -57,7 +57,11 @@ module Beetle
|
|
57
57
|
|
58
58
|
class HiredisLoadedTest < Minitest::Test
|
59
59
|
test "should be using hiredis instead of the redis ruby backend" do
|
60
|
-
|
60
|
+
if Redis::VERSION < "5.0"
|
61
|
+
assert defined?(Hiredis)
|
62
|
+
else
|
63
|
+
assert_equal RedisClient.default_driver, RedisClient::HiredisConnection
|
64
|
+
end
|
61
65
|
end
|
62
66
|
end
|
63
67
|
|
@@ -41,9 +41,9 @@ module Beetle
|
|
41
41
|
|
42
42
|
test "stop! should close all connections if the reactor is not running" do
|
43
43
|
connection1 = mock('conection1')
|
44
|
-
connection1.expects(:close)
|
44
|
+
connection1.expects(:close)
|
45
45
|
connection2 = mock('connection2')
|
46
|
-
connection2.expects(:close)
|
46
|
+
connection2.expects(:close)
|
47
47
|
@sub.instance_variable_set "@connections", [["server1", connection1], ["server2", connection2]]
|
48
48
|
EM.expects(:reactor_running?).returns(false)
|
49
49
|
@sub.send(:stop!)
|
@@ -52,7 +52,9 @@ module Beetle
|
|
52
52
|
|
53
53
|
class SubscriberPauseAndResumeTest < Minitest::Test
|
54
54
|
def setup
|
55
|
-
@
|
55
|
+
@config = Configuration.new
|
56
|
+
@config.servers = "localhost:5672"
|
57
|
+
@client = Client.new(@config)
|
56
58
|
@sub = @client.send(:subscriber)
|
57
59
|
@sub.servers << "localhost:7777"
|
58
60
|
@server1, @server2 = @sub.servers
|
@@ -342,7 +344,9 @@ module Beetle
|
|
342
344
|
|
343
345
|
class SubscriptionTest < Minitest::Test
|
344
346
|
def setup
|
345
|
-
@
|
347
|
+
@config = Configuration.new
|
348
|
+
@config.servers = "locahost:5672"
|
349
|
+
@client = Client.new(@config)
|
346
350
|
@sub = @client.send(:subscriber)
|
347
351
|
end
|
348
352
|
|