Skip to content

Commit dc5f805

Browse files
committed
Merge pull request #505 from redis/sentinel-tests
Sentinel tests + Hiredis fix
2 parents 179a6c4 + 021af54 commit dc5f805

File tree

4 files changed

+212
-15
lines changed

4 files changed

+212
-15
lines changed

lib/redis/client.rb

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -317,9 +317,9 @@ def establish_connection
317317
server = @connector.resolve.dup
318318

319319
@options[:host] = server[:host]
320-
@options[:port] = server[:port]
320+
@options[:port] = Integer(server[:port]) if server.include?(:port)
321321

322-
@connection = @options[:driver].connect(server)
322+
@connection = @options[:driver].connect(@options)
323323
@pending_reads = 0
324324
rescue TimeoutError,
325325
Errno::ECONNREFUSED,
@@ -366,6 +366,8 @@ def ensure_connected
366366
end
367367

368368
def _parse_options(options)
369+
return options if options[:_parsed]
370+
369371
defaults = DEFAULTS.dup
370372
options = options.dup
371373

@@ -450,6 +452,8 @@ def _parse_options(options)
450452
end
451453
end
452454

455+
options[:_parsed] = true
456+
453457
options
454458
end
455459

@@ -470,7 +474,7 @@ def _parse_driver(driver)
470474

471475
class Connector
472476
def initialize(options)
473-
@options = options
477+
@options = options.dup
474478
end
475479

476480
def resolve
@@ -484,9 +488,12 @@ class Sentinel < Connector
484488
def initialize(options)
485489
super(options)
486490

487-
@sentinels = options.fetch(:sentinels).dup
488-
@role = options.fetch(:role, "master").to_s
489-
@master = options[:host]
491+
@options[:password] = DEFAULTS.fetch(:password)
492+
@options[:db] = DEFAULTS.fetch(:db)
493+
494+
@sentinels = @options.delete(:sentinels).dup
495+
@role = @options.fetch(:role, "master").to_s
496+
@master = @options[:host]
490497
end
491498

492499
def check(client)
@@ -501,7 +508,7 @@ def check(client)
501508
end
502509

503510
if role != @role
504-
disconnect
511+
client.disconnect
505512
raise ConnectionError, "Instance role mismatch. Expected #{@role}, got #{role}."
506513
end
507514
end
@@ -521,7 +528,10 @@ def resolve
521528

522529
def sentinel_detect
523530
@sentinels.each do |sentinel|
524-
client = Client.new(:host => sentinel[:host], :port => sentinel[:port], :timeout => 0.3)
531+
client = Client.new(@options.merge({
532+
:host => sentinel[:host],
533+
:port => sentinel[:port]
534+
}))
525535

526536
begin
527537
if result = yield(client)

lib/redis/connection/hiredis.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@ class Hiredis
99

1010
def self.connect(config)
1111
connection = ::Hiredis::Connection.new
12+
connect_timeout = (config.fetch(:connect_timeout, 0) * 1_000_000).to_i
1213

1314
if config[:scheme] == "unix"
14-
connection.connect_unix(config[:path], Integer(config[:connect_timeout] * 1_000_000))
15+
connection.connect_unix(config[:path], connect_timeout)
1516
else
16-
connection.connect(config[:host], config[:port], Integer(config[:connect_timeout] * 1_000_000))
17+
connection.connect(config[:host], config[:port], connect_timeout)
1718
end
1819

1920
instance = new(connection)

test/sentinel_test.rb

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
# encoding: UTF-8
2+
3+
require File.expand_path("helper", File.dirname(__FILE__))
4+
5+
class SentinalTest < Test::Unit::TestCase
6+
7+
include Helper::Client
8+
9+
def test_sentinel_connection
10+
sentinels = [{:host => "127.0.0.1", :port => 26381},
11+
{:host => "127.0.0.1", :port => 26382}]
12+
13+
commands = {
14+
:s1 => [],
15+
:s2 => [],
16+
}
17+
18+
handler = lambda do |id|
19+
{
20+
:sentinel => lambda do |command, *args|
21+
commands[id] << [command, *args]
22+
["127.0.0.1", "6381"]
23+
end
24+
}
25+
end
26+
27+
RedisMock.start(handler.call(:s1), {}, 26381) do
28+
RedisMock.start(handler.call(:s2), {}, 26382) do
29+
redis = Redis.new(:url => "redis://master1", :sentinels => sentinels, :role => :master)
30+
31+
assert redis.ping
32+
end
33+
end
34+
35+
assert_equal commands[:s1], [%w[get-master-addr-by-name master1]]
36+
assert_equal commands[:s2], []
37+
end
38+
39+
def test_sentinel_failover
40+
sentinels = [{:host => "127.0.0.1", :port => 26381},
41+
{:host => "127.0.0.1", :port => 26382}]
42+
43+
commands = {
44+
:s1 => [],
45+
:s2 => [],
46+
}
47+
48+
s1 = {
49+
:sentinel => lambda do |command, *args|
50+
commands[:s1] << [command, *args]
51+
"$-1" # Nil
52+
end
53+
}
54+
55+
s2 = {
56+
:sentinel => lambda do |command, *args|
57+
commands[:s2] << [command, *args]
58+
["127.0.0.1", "6381"]
59+
end
60+
}
61+
62+
RedisMock.start(s1, {}, 26381) do
63+
RedisMock.start(s2, {}, 26382) do
64+
redis = Redis.new(:url => "redis://master1", :sentinels => sentinels, :role => :master)
65+
66+
assert redis.ping
67+
end
68+
end
69+
70+
assert_equal commands[:s1], [%w[get-master-addr-by-name master1]]
71+
assert_equal commands[:s2], [%w[get-master-addr-by-name master1]]
72+
end
73+
74+
def test_sentinel_failover_prioritize_healthy_sentinel
75+
sentinels = [{:host => "127.0.0.1", :port => 26381},
76+
{:host => "127.0.0.1", :port => 26382}]
77+
78+
commands = {
79+
:s1 => [],
80+
:s2 => [],
81+
}
82+
83+
s1 = {
84+
:sentinel => lambda do |command, *args|
85+
commands[:s1] << [command, *args]
86+
"$-1" # Nil
87+
end
88+
}
89+
90+
s2 = {
91+
:sentinel => lambda do |command, *args|
92+
commands[:s2] << [command, *args]
93+
["127.0.0.1", "6381"]
94+
end
95+
}
96+
97+
RedisMock.start(s1, {}, 26381) do
98+
RedisMock.start(s2, {}, 26382) do
99+
redis = Redis.new(:url => "redis://master1", :sentinels => sentinels, :role => :master)
100+
101+
assert redis.ping
102+
103+
redis.quit
104+
105+
assert redis.ping
106+
end
107+
end
108+
109+
assert_equal commands[:s1], [%w[get-master-addr-by-name master1]]
110+
assert_equal commands[:s2], [%w[get-master-addr-by-name master1], %w[get-master-addr-by-name master1]]
111+
end
112+
113+
def test_sentinel_with_non_sentinel_options
114+
sentinels = [{:host => "127.0.0.1", :port => 26381}]
115+
116+
commands = {
117+
:s1 => [],
118+
:m1 => []
119+
}
120+
121+
sentinel = {
122+
:auth => lambda do |pass|
123+
commands[:s1] << ["auth", pass]
124+
"-ERR unknown command 'auth'"
125+
end,
126+
:select => lambda do |db|
127+
commands[:s1] << ["select", db]
128+
"-ERR unknown command 'select'"
129+
end,
130+
:sentinel => lambda do |command, *args|
131+
commands[:s1] << [command, *args]
132+
["127.0.0.1", "6382"]
133+
end
134+
}
135+
136+
master = {
137+
:auth => lambda do |pass|
138+
commands[:m1] << ["auth", pass]
139+
"+OK"
140+
end,
141+
:role => lambda do
142+
commands[:m1] << ["role"]
143+
["master"]
144+
end
145+
}
146+
147+
RedisMock.start(master, {}, 6382) do
148+
RedisMock.start(sentinel, {}, 26381) do
149+
redis = Redis.new(:url => "redis://:foo@master1/15", :sentinels => sentinels, :role => :master)
150+
151+
assert redis.ping
152+
end
153+
end
154+
155+
assert_equal [%w[get-master-addr-by-name master1]], commands[:s1]
156+
assert_equal [%w[auth foo], %w[role]], commands[:m1]
157+
end
158+
159+
def test_sentinel_role_mismatch
160+
sentinels = [{:host => "127.0.0.1", :port => 26381}]
161+
162+
sentinel = {
163+
:sentinel => lambda do |command, *args|
164+
["127.0.0.1", "6382"]
165+
end
166+
}
167+
168+
master = {
169+
:role => lambda do
170+
["slave"]
171+
end
172+
}
173+
174+
ex = assert_raise(Redis::ConnectionError) do
175+
RedisMock.start(master, {}, 6382) do
176+
RedisMock.start(sentinel, {}, 26381) do
177+
redis = Redis.new(:url => "redis://master1", :sentinels => sentinels, :role => :master)
178+
179+
assert redis.ping
180+
end
181+
end
182+
end
183+
184+
assert_match /Instance role mismatch/, ex.message
185+
end
186+
end

test/support/redis_mock.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,13 @@ def run
5959
# # Every connection will be closed immediately
6060
# end
6161
#
62-
def self.start_with_handler(blk, options = {})
63-
server = Server.new(MOCK_PORT, options)
62+
def self.start_with_handler(blk, options = {}, port = MOCK_PORT)
63+
server = Server.new(port, options)
6464

6565
begin
6666
server.start(&blk)
6767

68-
yield(MOCK_PORT)
68+
yield(port)
6969

7070
ensure
7171
server.shutdown
@@ -81,7 +81,7 @@ def self.start_with_handler(blk, options = {})
8181
# assert_equal "PONG", Redis.new(:port => MOCK_PORT).ping
8282
# end
8383
#
84-
def self.start(commands, options = {}, &blk)
84+
def self.start(commands, options = {}, port = MOCK_PORT, &blk)
8585
handler = lambda do |session|
8686
while line = session.gets
8787
argv = Array.new(line[1..-3].to_i) do
@@ -116,6 +116,6 @@ def self.start(commands, options = {}, &blk)
116116
end
117117
end
118118

119-
start_with_handler(handler, options, &blk)
119+
start_with_handler(handler, options, port, &blk)
120120
end
121121
end

0 commit comments

Comments
 (0)