amqp-failover 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +6 -0
- data/.gitignore +28 -0
- data/.rspec +2 -0
- data/.rvmrc +1 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.md +104 -0
- data/Rakefile +56 -0
- data/amqp-failover.gemspec +30 -0
- data/lib/amqp/failover/config.rb +42 -0
- data/lib/amqp/failover/configurations.rb +92 -0
- data/lib/amqp/failover/ext/amqp/client.rb +67 -0
- data/lib/amqp/failover/logger.rb +31 -0
- data/lib/amqp/failover/server_discovery.rb +45 -0
- data/lib/amqp/failover/version.rb +7 -0
- data/lib/amqp/failover.rb +111 -0
- data/lib/amqp/failover_client.rb +83 -0
- data/spec/integration/basic_spec.rb +59 -0
- data/spec/integration/failover_spec.rb +141 -0
- data/spec/logger_helper.rb +18 -0
- data/spec/server_helper.rb +77 -0
- data/spec/spec_helper.rb +49 -0
- data/spec/unit/amqp/failover/config_spec.rb +67 -0
- data/spec/unit/amqp/failover/configurations_spec.rb +79 -0
- data/spec/unit/amqp/failover/server_discovery_helper.rb +31 -0
- data/spec/unit/amqp/failover/server_discovery_spec.rb +56 -0
- data/spec/unit/amqp/failover_spec.rb +69 -0
- metadata +212 -0
@@ -0,0 +1,83 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module AMQP
|
4
|
+
module FailoverClient
|
5
|
+
include AMQP::BasicClient
|
6
|
+
|
7
|
+
attr_accessor :failover
|
8
|
+
attr_reader :fallback_monitor
|
9
|
+
|
10
|
+
attr_accessor :settings
|
11
|
+
attr_accessor :on_disconnect
|
12
|
+
|
13
|
+
def self.extended(base)
|
14
|
+
if (base.failover = base.settings.delete(:failover))
|
15
|
+
base.on_disconnect = base.method(:disconnected)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def failover_switch
|
20
|
+
if (new_settings = @failover.from(@settings))
|
21
|
+
log_message = "Could not connect to or lost connection to server #{@settings[:host]}:#{@settings[:port]}. " +
|
22
|
+
"Attempting connection to: #{new_settings[:host]}:#{new_settings[:port]}"
|
23
|
+
logger.error(log_message)
|
24
|
+
logger.info(log_message)
|
25
|
+
|
26
|
+
if @failover.options[:fallback] && @failover.primary == @settings
|
27
|
+
fallback(@failover.primary, @failover.fallback_interval)
|
28
|
+
end
|
29
|
+
@settings = new_settings
|
30
|
+
reconnect
|
31
|
+
else
|
32
|
+
raise Error, "Could not connect to server #{@settings[:host]}:#{@settings[:port]}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def logger
|
37
|
+
Failover.logger
|
38
|
+
end
|
39
|
+
|
40
|
+
def configs
|
41
|
+
@failover.configs if @failover
|
42
|
+
end
|
43
|
+
|
44
|
+
def clean_exit(msg = nil)
|
45
|
+
msg ||= "clean exit"
|
46
|
+
logger.info(msg)
|
47
|
+
logger.error(msg)
|
48
|
+
Process.exit
|
49
|
+
end
|
50
|
+
|
51
|
+
def fallback(conf = {}, retry_interval = nil)
|
52
|
+
@fallback_monitor = Failover::ServerDiscovery.monitor(conf, retry_interval) do
|
53
|
+
fallback_callback.call(conf, retry_interval)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def fallback_callback
|
58
|
+
#TODO: Figure out a way to artificially trigger EM to disconnect on fallback without channels being closed.
|
59
|
+
@fallback_callback ||= proc { |conf, retry_interval|
|
60
|
+
clean_exit("Primary server (#{conf[:host]}:#{conf[:port]}) is back. " +
|
61
|
+
"Performing clean exit to be relaunched with primary config.")
|
62
|
+
}
|
63
|
+
end
|
64
|
+
attr_writer :fallback_callback
|
65
|
+
|
66
|
+
#TODO: Figure out why I originally needed this
|
67
|
+
# def process_frame(frame)
|
68
|
+
# if mq = channels[frame.channel]
|
69
|
+
# mq.process_frame(frame)
|
70
|
+
# return
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
# if frame.is_a?(AMQP::Frame::Method) && (method = frame.payload).is_a?(AMQP::Protocol::Connection::Close)
|
74
|
+
# if method.reply_text =~ /^NOT_ALLOWED/
|
75
|
+
# raise AMQP::Error, "#{method.reply_text} in #{::AMQP::Protocol.classes[method.class_id].methods[method.method_id]}"
|
76
|
+
# end
|
77
|
+
# end
|
78
|
+
# super(frame)
|
79
|
+
# end
|
80
|
+
|
81
|
+
end # FailoverClient
|
82
|
+
end # AMQP
|
83
|
+
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__))
|
3
|
+
|
4
|
+
require 'spec_helper'
|
5
|
+
require 'mq'
|
6
|
+
require 'amqp'
|
7
|
+
require 'amqp/server'
|
8
|
+
require 'server_helper'
|
9
|
+
|
10
|
+
describe "Basic AMQP connection with FailoverClient loaded" do
|
11
|
+
|
12
|
+
after(:each) do
|
13
|
+
ServerHelper.clear_logs
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should be using FailoverClient" do
|
17
|
+
AMQP.client.should == AMQP::FailoverClient
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should be able to connect" do
|
21
|
+
EM.run {
|
22
|
+
port = 15672
|
23
|
+
timeout = 2
|
24
|
+
serv = start_server(port)
|
25
|
+
EM.add_timer(1.5) {
|
26
|
+
conn = AMQP.connect(:host => 'localhost', :port => 15672)
|
27
|
+
EM.add_timer(0.1) {
|
28
|
+
conn.should be_connected
|
29
|
+
serv.stop
|
30
|
+
log = serv.log
|
31
|
+
log.size.should == 3
|
32
|
+
(0..2).each { |i| log[i]['method'].should == "send" }
|
33
|
+
log[0]['class'].should == 'AMQP::Protocol::Connection::Start'
|
34
|
+
log[1]['class'].should == 'AMQP::Protocol::Connection::Tune'
|
35
|
+
log[2]['class'].should == 'AMQP::Protocol::Connection::OpenOk'
|
36
|
+
EM.stop
|
37
|
+
}
|
38
|
+
}
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should be able to connect and get disconnected" do
|
43
|
+
EM.run {
|
44
|
+
serv = start_server(25672)
|
45
|
+
EM.add_timer(0.1) {
|
46
|
+
conn = AMQP.connect(:host => 'localhost', :port => 25672)
|
47
|
+
EM.add_timer(0.1) {
|
48
|
+
conn.should be_connected
|
49
|
+
serv.stop
|
50
|
+
EM.add_timer(0.1) {
|
51
|
+
conn.should_not be_connected
|
52
|
+
EM.stop
|
53
|
+
}
|
54
|
+
}
|
55
|
+
}
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__))
|
3
|
+
|
4
|
+
require 'spec_helper'
|
5
|
+
require 'amqp/server'
|
6
|
+
require 'server_helper'
|
7
|
+
require 'logger_helper'
|
8
|
+
|
9
|
+
describe "Failover support loaded into AMQP gem" do
|
10
|
+
|
11
|
+
before(:each) do
|
12
|
+
@flog = LoggerHelper.new
|
13
|
+
AMQP::Failover.logger = @flog
|
14
|
+
end
|
15
|
+
|
16
|
+
after(:each) do
|
17
|
+
ServerHelper.clear_logs
|
18
|
+
AMQP::Failover.logger = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should be able to connect" do
|
22
|
+
port1 = 15672
|
23
|
+
EM.run {
|
24
|
+
serv = start_server(port1)
|
25
|
+
EM.add_timer(0.1) {
|
26
|
+
conn = AMQP.connect(:host => 'localhost', :port => port1)
|
27
|
+
conn.failover.should be_nil
|
28
|
+
EM.add_timer(0.1) {
|
29
|
+
conn.should be_connected
|
30
|
+
EM.stop
|
31
|
+
}
|
32
|
+
}
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should be able to connect and failover" do
|
37
|
+
port1 = 25672
|
38
|
+
port2 = 35672
|
39
|
+
EM.run {
|
40
|
+
# start mock amqp servers
|
41
|
+
serv1 = start_server(port1)
|
42
|
+
serv2 = start_server(port2)
|
43
|
+
EM.add_timer(0.1) {
|
44
|
+
# start amqp client connection and make sure it's picked the right config
|
45
|
+
conn = AMQP.connect({:hosts => [{:port => port1}, {:port => port2}]})
|
46
|
+
conn.failover.primary[:port].should == port1
|
47
|
+
conn.settings[:port].should == port1
|
48
|
+
conn.settings.should == conn.failover.primary
|
49
|
+
EM.add_timer(0.1) {
|
50
|
+
# make sure client connected to the correct server, then kill server
|
51
|
+
conn.should be_connected
|
52
|
+
serv1.log.should have(3).items
|
53
|
+
serv2.log.should have(0).items
|
54
|
+
serv1.stop
|
55
|
+
EM.add_timer(0.1) {
|
56
|
+
# make sure client performed a failover when primary server died
|
57
|
+
conn.should be_connected
|
58
|
+
[:error, :info].each do |i|
|
59
|
+
@flog.send("#{i}_log").should have(1).item
|
60
|
+
@flog.send("#{i}_log")[0][0].should match(/connect to or lost connection.+#{port1}.+attempting connection.+#{port2}/i)
|
61
|
+
end
|
62
|
+
conn.settings[:port].should == port2
|
63
|
+
serv1.log.should have(3).items
|
64
|
+
serv2.log.should have(3).items
|
65
|
+
conn.close
|
66
|
+
EM.add_timer(0.1) {
|
67
|
+
serv2.stop
|
68
|
+
EM.stop
|
69
|
+
}
|
70
|
+
}
|
71
|
+
}
|
72
|
+
}
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should be able to fallback when primary server returns" do
|
77
|
+
port1 = 45672
|
78
|
+
port2 = 55672
|
79
|
+
lambda {
|
80
|
+
EM.run {
|
81
|
+
# start mock amqp servers
|
82
|
+
serv1 = start_server(port1)
|
83
|
+
serv2 = start_server(port2)
|
84
|
+
EM.add_timer(0.1) {
|
85
|
+
# start amqp client connection and make sure it's picked the right config
|
86
|
+
conn = AMQP.connect({:hosts => [{:port => port1}, {:port => port2}], :fallback => true, :fallback_interval => 0.1})
|
87
|
+
conn.failover.primary[:port].should == port1
|
88
|
+
conn.settings[:port].should == port1
|
89
|
+
conn.settings.should == conn.failover.primary
|
90
|
+
EM.add_timer(0.1) {
|
91
|
+
# make sure client connected to the correct server, then kill server
|
92
|
+
conn.should be_connected
|
93
|
+
serv1.log.should have(3).items
|
94
|
+
serv2.log.should have(0).items
|
95
|
+
serv1.stop
|
96
|
+
EM.add_timer(0.1) {
|
97
|
+
# make sure client performed a failover when primary server died
|
98
|
+
conn.should be_connected
|
99
|
+
[:error, :info].each do |i|
|
100
|
+
@flog.send("#{i}_log").should have(1).item
|
101
|
+
@flog.send("#{i}_log")[0][0].should match(/connect to or lost connection.+#{port1}.+attempting connection.+#{port2}/i)
|
102
|
+
end
|
103
|
+
conn.settings[:port].should == port2
|
104
|
+
serv1.log.should have(3).items
|
105
|
+
serv2.log.should have(3).items
|
106
|
+
serv3 = start_server(port1)
|
107
|
+
EM.add_timer(0.2) {
|
108
|
+
# by this point client should have raised a SystemExit exception
|
109
|
+
serv2.stop
|
110
|
+
EM.stop
|
111
|
+
}
|
112
|
+
}
|
113
|
+
}
|
114
|
+
}
|
115
|
+
}
|
116
|
+
}.should raise_error(SystemExit, "exit")
|
117
|
+
[:error, :info].each do |i|
|
118
|
+
@flog.send("#{i}_log").should have(2).item
|
119
|
+
@flog.send("#{i}_log")[1][0].should match(/primary server.+45672.+performing clean exit/i)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
it "should abide to :primary_config option" do
|
124
|
+
port1 = 75672
|
125
|
+
port2 = 65672
|
126
|
+
EM.run {
|
127
|
+
serv = start_server(port1)
|
128
|
+
EM.add_timer(0.1) {
|
129
|
+
conn = AMQP.connect({:hosts => [{:port => port1}, {:port => port2}], :primary_config => 1})
|
130
|
+
conn.failover.primary[:port].should == port2
|
131
|
+
conn.settings[:port].should == port2
|
132
|
+
conn.settings.should == conn.failover.primary
|
133
|
+
EM.add_timer(0.1) {
|
134
|
+
conn.should be_connected
|
135
|
+
EM.stop
|
136
|
+
}
|
137
|
+
}
|
138
|
+
}
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class LoggerHelper
|
4
|
+
|
5
|
+
attr_accessor :error_log
|
6
|
+
attr_accessor :info_log
|
7
|
+
|
8
|
+
def info(*args)
|
9
|
+
@info_log ||= []
|
10
|
+
@info_log << args
|
11
|
+
end
|
12
|
+
|
13
|
+
def error(*args)
|
14
|
+
@error_log ||= []
|
15
|
+
@error_log << args
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'mq'
|
5
|
+
require 'amqp'
|
6
|
+
require 'amqp/server'
|
7
|
+
require 'json'
|
8
|
+
|
9
|
+
class ServerHelper
|
10
|
+
|
11
|
+
attr_accessor :stdin
|
12
|
+
attr_accessor :stdout
|
13
|
+
attr_accessor :stderr
|
14
|
+
attr_accessor :pid
|
15
|
+
|
16
|
+
|
17
|
+
def initialize(port = nil, timeout = nil)
|
18
|
+
@port = port
|
19
|
+
@timout = timeout
|
20
|
+
File.open(log_file, 'w') {}
|
21
|
+
@pid = start(port, timeout)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.clear_logs
|
25
|
+
Dir.glob(File.expand_path('server_helper*.log', File.dirname(__FILE__))).each do |file|
|
26
|
+
File.delete(file)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def start(port = nil, timeout = nil)
|
31
|
+
port ||= 15672
|
32
|
+
timeout ||= 2
|
33
|
+
EM.fork_reactor {
|
34
|
+
$PORT = port
|
35
|
+
EM.start_server('localhost', port, AmqpServer)
|
36
|
+
EM.add_timer(timeout) { EM.stop }
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
def stop
|
41
|
+
Process.kill('TERM', @pid)
|
42
|
+
end
|
43
|
+
|
44
|
+
def kill
|
45
|
+
Process.kill('KILL', @pid)
|
46
|
+
end
|
47
|
+
|
48
|
+
def log
|
49
|
+
File.open(log_file).to_a.map{ |l| JSON.parse(l) }
|
50
|
+
end
|
51
|
+
|
52
|
+
def log_file
|
53
|
+
File.expand_path("server_helper-port#{@port}.log", File.dirname(__FILE__))
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
module AmqpServer
|
59
|
+
include AMQP::Server
|
60
|
+
|
61
|
+
# customize log output
|
62
|
+
def log(*args)
|
63
|
+
args = {:method => args[0], :class => args[1].payload.class, :pid => Process.pid}
|
64
|
+
filename = File.expand_path("server_helper-port#{$PORT}.log", File.dirname(__FILE__))
|
65
|
+
File.open(filename, 'a') do |f|
|
66
|
+
f.write("#{args.to_json}\n")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
#
|
72
|
+
# Helper methods
|
73
|
+
#
|
74
|
+
|
75
|
+
def start_server(port = nil, timeout = nil)
|
76
|
+
ServerHelper.new(port, timeout)
|
77
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# add project-relative load paths
|
4
|
+
$LOAD_PATH.unshift File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
|
5
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__))
|
6
|
+
|
7
|
+
# require stuff
|
8
|
+
require 'rubygems'
|
9
|
+
|
10
|
+
begin
|
11
|
+
require 'mq'
|
12
|
+
rescue LoadError => e
|
13
|
+
require 'amqp'
|
14
|
+
end
|
15
|
+
require 'amqp/failover'
|
16
|
+
|
17
|
+
require 'rspec'
|
18
|
+
require 'rspec/autorun'
|
19
|
+
|
20
|
+
|
21
|
+
#
|
22
|
+
# Helper methods
|
23
|
+
#
|
24
|
+
|
25
|
+
def wait_while(timeout = 10, retry_interval = 0.1, &block)
|
26
|
+
start = Time.now
|
27
|
+
while block.call
|
28
|
+
break if (Time.now - start).to_i >= timeout
|
29
|
+
sleep(retry_interval)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# stolen from Pid::running? from daemons gem
|
34
|
+
def pid_running?(pid)
|
35
|
+
return false unless pid
|
36
|
+
|
37
|
+
# Check if process is in existence
|
38
|
+
# The simplest way to do this is to send signal '0'
|
39
|
+
# (which is a single system call) that doesn't actually
|
40
|
+
# send a signal
|
41
|
+
begin
|
42
|
+
Process.kill(0, pid)
|
43
|
+
return true
|
44
|
+
rescue Errno::ESRCH
|
45
|
+
return false
|
46
|
+
rescue ::Exception # for example on EPERM (process exists but does not belong to us)
|
47
|
+
return true
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__))
|
3
|
+
|
4
|
+
require 'spec_helper'
|
5
|
+
|
6
|
+
describe 'AMQP::Failover::Config' do
|
7
|
+
|
8
|
+
before(:each) do
|
9
|
+
configs = [
|
10
|
+
{:host => 'rabbit0.local'},
|
11
|
+
{:host => 'rabbit1.local'},
|
12
|
+
{:host => 'rabbit2.local', :port => 5673}
|
13
|
+
]
|
14
|
+
@configs = configs.map { |conf| AMQP.settings.merge(conf) }
|
15
|
+
@fail = AMQP::Failover.new(@configs)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should initialize" do
|
19
|
+
fail = AMQP::Failover::Config.new(@configs[0])
|
20
|
+
fail.should == @configs[0]
|
21
|
+
fail.last_fail.should be_nil
|
22
|
+
|
23
|
+
now = Time.now
|
24
|
+
fail = AMQP::Failover::Config.new(@configs[1], now)
|
25
|
+
fail.should == @configs[1]
|
26
|
+
fail.last_fail.should == now
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should order properly with #<=>" do
|
30
|
+
one_hour_ago = (Time.now - 3600)
|
31
|
+
two_hours_ago = (Time.now - 7200)
|
32
|
+
|
33
|
+
fail = [ AMQP::Failover::Config.new(@configs[0]),
|
34
|
+
AMQP::Failover::Config.new(@configs[1], one_hour_ago),
|
35
|
+
AMQP::Failover::Config.new(@configs[2], two_hours_ago) ]
|
36
|
+
|
37
|
+
(fail[1] <=> fail[0]).should == -1
|
38
|
+
(fail[0] <=> fail[0]).should == 0
|
39
|
+
(fail[0] <=> fail[1]).should == 1
|
40
|
+
|
41
|
+
(fail[1] <=> fail[2]).should == -1
|
42
|
+
(fail[1] <=> fail[1]).should == 0
|
43
|
+
(fail[2] <=> fail[1]).should == 1
|
44
|
+
|
45
|
+
fail.sort[0].last_fail.should == one_hour_ago
|
46
|
+
fail.sort[1].last_fail.should == two_hours_ago
|
47
|
+
fail.sort[2].last_fail.should == nil
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should be ordered by last_fail" do
|
51
|
+
result = [ AMQP::Failover::Config.new(@configs[1], (Time.now - 60)),
|
52
|
+
AMQP::Failover::Config.new(@configs[2], (Time.now - (60*25))),
|
53
|
+
AMQP::Failover::Config.new(@configs[0], (Time.now - 3600)) ]
|
54
|
+
|
55
|
+
origin = [ AMQP::Failover::Config.new(@configs[0], (Time.now - 3600)),
|
56
|
+
AMQP::Failover::Config.new(@configs[1], (Time.now - 60)),
|
57
|
+
AMQP::Failover::Config.new(@configs[2], (Time.now - (60*25))) ]
|
58
|
+
origin.sort.should == result
|
59
|
+
|
60
|
+
origin = [ AMQP::Failover::Config.new(@configs[0]),
|
61
|
+
AMQP::Failover::Config.new(@configs[1], (Time.now - 60)),
|
62
|
+
AMQP::Failover::Config.new(@configs[2], (Time.now - (60*25))) ]
|
63
|
+
origin.sort.should == result
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__))
|
3
|
+
|
4
|
+
require 'spec_helper'
|
5
|
+
|
6
|
+
describe 'AMQP::Failover::Configurations' do
|
7
|
+
|
8
|
+
before(:each) do
|
9
|
+
@conf = AMQP::Failover::Configurations.new
|
10
|
+
@raw_configs = [
|
11
|
+
{:host => 'rabbit0.local'},
|
12
|
+
{:host => 'rabbit1.local'},
|
13
|
+
{:host => 'rabbit2.local', :port => 5673}
|
14
|
+
]
|
15
|
+
@configs = @raw_configs.map { |conf| AMQP.settings.merge(conf) }
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should initialize" do
|
19
|
+
confs = AMQP::Failover::Configurations.new(@raw_configs)
|
20
|
+
confs.each_with_index do |conf, i|
|
21
|
+
conf.should be_a(AMQP::Failover::Config)
|
22
|
+
conf.should == @configs[i]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should set and get configs" do
|
27
|
+
@conf.primary_ref.should == 0
|
28
|
+
@conf.should have(0).items
|
29
|
+
|
30
|
+
@conf.set(@raw_configs[0])
|
31
|
+
@conf.should have(1).items
|
32
|
+
@conf.get(0).should == @configs[0]
|
33
|
+
@conf[0].should == @configs[0]
|
34
|
+
|
35
|
+
@conf.set(@raw_configs[1])
|
36
|
+
@conf.should have(2).items
|
37
|
+
@conf.get(1).should == @configs[1]
|
38
|
+
@conf[1].should == @configs[1]
|
39
|
+
|
40
|
+
# should just create a ref, as config exists
|
41
|
+
@conf.set(@raw_configs[1], :the_one)
|
42
|
+
@conf.should have(2).items
|
43
|
+
@conf.get(1).should == @configs[1]
|
44
|
+
@conf[:the_one].should == @configs[1]
|
45
|
+
|
46
|
+
@conf.load_array(@raw_configs)
|
47
|
+
@conf.should have(3).items
|
48
|
+
@conf.primary.should == @configs[0]
|
49
|
+
@conf.primary_ref = 1
|
50
|
+
@conf.primary.should == @configs[1]
|
51
|
+
@conf[:primary].should == @configs[1]
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should #find_next" do
|
55
|
+
@conf.load(@raw_configs)
|
56
|
+
@conf.should have(3).items
|
57
|
+
@conf.find_next(@configs[0]).should == @configs[1]
|
58
|
+
@conf.find_next(@configs[1]).should == @configs[2]
|
59
|
+
@conf.find_next(@configs[2]).should == @configs[0]
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should #load_hash" do
|
63
|
+
@conf.should have(0).items
|
64
|
+
@conf.load_hash(@raw_configs[0])
|
65
|
+
@conf.should have(1).items
|
66
|
+
@conf.primary.should == @configs[0]
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should #load_array" do
|
70
|
+
@conf.load_hash(:host => 'rabbid-rabbit')
|
71
|
+
@conf.should have(1).items
|
72
|
+
@conf.load_array(@raw_configs)
|
73
|
+
@conf.should have(3).items
|
74
|
+
@conf.should == @configs
|
75
|
+
@conf.primary.should == @configs[0]
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class ServerDiscoveryHelper < AMQP::Failover::ServerDiscovery
|
2
|
+
|
3
|
+
class << self
|
4
|
+
alias :real_start_monitoring :start_monitoring
|
5
|
+
def start_monitoring(*args, &block)
|
6
|
+
$called << :start_monitoring
|
7
|
+
real_start_monitoring(*args, &block)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
alias :real_initialize :initialize
|
12
|
+
def initialize(*args)
|
13
|
+
$called << :initialize
|
14
|
+
EM.start_server('127.0.0.1', 9999) if $start_count == 2
|
15
|
+
$start_count += 1
|
16
|
+
real_initialize(*args)
|
17
|
+
end
|
18
|
+
|
19
|
+
alias :real_connection_completed :connection_completed
|
20
|
+
def connection_completed
|
21
|
+
$called << :connection_completed
|
22
|
+
real_connection_completed
|
23
|
+
end
|
24
|
+
|
25
|
+
alias :real_close_connection :close_connection
|
26
|
+
def close_connection
|
27
|
+
$called << :close_connection
|
28
|
+
real_close_connection
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__))
|
3
|
+
|
4
|
+
require 'spec_helper'
|
5
|
+
require 'server_discovery_helper'
|
6
|
+
|
7
|
+
describe 'AMQP::Failover::ServerDiscovery' do
|
8
|
+
|
9
|
+
before(:each) do
|
10
|
+
$called = []
|
11
|
+
$start_count = 0
|
12
|
+
@args = { :host => 'localhost', :port => 9999 }
|
13
|
+
@retry_interval = 0.01
|
14
|
+
end
|
15
|
+
|
16
|
+
after(:all) do
|
17
|
+
$called = nil
|
18
|
+
$start_count = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should initialize" do
|
22
|
+
EM.run {
|
23
|
+
EM.start_server('127.0.0.1', 9999)
|
24
|
+
@mon = ServerDiscoveryHelper.monitor(@args, @retry_interval) do
|
25
|
+
$called << :done_block
|
26
|
+
EM.stop_event_loop
|
27
|
+
end
|
28
|
+
}
|
29
|
+
$start_count.should == 1
|
30
|
+
$called.should have(5).items
|
31
|
+
$called.uniq.should have(5).items
|
32
|
+
$called.should include(:start_monitoring)
|
33
|
+
$called.should include(:initialize)
|
34
|
+
$called.should include(:connection_completed)
|
35
|
+
$called.should include(:close_connection)
|
36
|
+
$called.should include(:done_block)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should retry on error" do
|
40
|
+
EM.run {
|
41
|
+
@mon = ServerDiscoveryHelper.monitor(@args, @retry_interval) do
|
42
|
+
$called << :done_block
|
43
|
+
EM.stop_event_loop
|
44
|
+
end
|
45
|
+
}
|
46
|
+
$start_count.should >= 3
|
47
|
+
$called.should have($start_count + 4).items
|
48
|
+
$called.uniq.should have(5).items
|
49
|
+
$called.should include(:start_monitoring)
|
50
|
+
$called.should include(:initialize)
|
51
|
+
$called.should include(:connection_completed)
|
52
|
+
$called.should include(:close_connection)
|
53
|
+
$called.should include(:done_block)
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|