beetle 3.5.1 → 3.5.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|