Skip to content

Commit 6041668

Browse files
authored
Merge pull request #814 from supercaracal/fix-sentinel-auth
Fix sentinel authentication for Redis5
2 parents 0b95bc0 + 20a57e0 commit 6041668

File tree

4 files changed

+107
-45
lines changed

4 files changed

+107
-45
lines changed

lib/redis/client.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -525,7 +525,6 @@ class Sentinel < Connector
525525
def initialize(options)
526526
super(options)
527527

528-
@options[:password] = DEFAULTS.fetch(:password)
529528
@options[:db] = DEFAULTS.fetch(:db)
530529

531530
@sentinels = @options.delete(:sentinels).dup
@@ -586,6 +585,11 @@ def sentinel_detect
586585
end
587586

588587
raise CannotConnectError, "No sentinels available."
588+
rescue Redis::CommandError => err
589+
# this feature is only available starting with Redis 5.0.1
590+
raise unless err.message.start_with?('ERR unknown command `auth`')
591+
@options[:password] = DEFAULTS.fetch(:password)
592+
retry
589593
end
590594

591595
def resolve_master

test/helper.rb

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# frozen_string_literal: true
2+
13
require "test/unit"
24
require "mocha/test_unit"
35
require "logger"
@@ -183,6 +185,40 @@ def _new_client(options = {})
183185
end
184186
end
185187

188+
module Sentinel
189+
include Generic
190+
191+
MASTER_PORT = PORT.to_s
192+
SLAVE_PORT = '6382'
193+
SENTINEL_PORT = '6400'
194+
SENTINEL_PORTS = %w[6400 6401 6402].freeze
195+
MASTER_NAME = 'master1'
196+
LOCALHOST = '127.0.0.1'
197+
198+
def build_sentinel_client(options = {})
199+
opts = { host: LOCALHOST, port: SENTINEL_PORT, timeout: TIMEOUT, logger: ::Logger.new(@log) }
200+
Redis.new(opts.merge(options))
201+
end
202+
203+
def build_slave_role_client(options = {})
204+
_new_client(options.merge(role: :slave))
205+
end
206+
207+
private
208+
209+
def _format_options(options = {})
210+
{
211+
url: "redis://#{MASTER_NAME}",
212+
sentinels: [{ host: LOCALHOST, port: SENTINEL_PORT }],
213+
role: :master, timeout: TIMEOUT, logger: ::Logger.new(@log)
214+
}.merge(options)
215+
end
216+
217+
def _new_client(options = {})
218+
Redis.new(_format_options(options).merge(driver: ENV['DRIVER']))
219+
end
220+
end
221+
186222
module Distributed
187223
include Generic
188224

test/sentinel_command_test.rb

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,7 @@
44

55
# @see https://redis.io/topics/sentinel#sentinel-commands Sentinel commands
66
class SentinelCommandsTest < Test::Unit::TestCase
7-
include Helper::Client
8-
9-
MASTER_PORT = PORT.to_s
10-
SLAVE_PORT = '6382'
11-
SENTINEL_PORT = '6400'
12-
MASTER_NAME = 'master1'
13-
LOCALHOST = '127.0.0.1'
14-
15-
def build_sentinel_client
16-
Redis.new(host: LOCALHOST, port: SENTINEL_PORT, timeout: TIMEOUT)
17-
end
7+
include Helper::Sentinel
188

199
def test_sentinel_command_master
2010
redis = build_sentinel_client
@@ -49,7 +39,7 @@ def test_sentinel_command_sentinels
4939
assert_equal result[0]['ip'], LOCALHOST
5040

5141
actual_ports = result.map { |r| r['port'] }.sort
52-
expected_ports = (SENTINEL_PORT.to_i + 1..SENTINEL_PORT.to_i + 2).map(&:to_s)
42+
expected_ports = SENTINEL_PORTS[1..-1]
5343
assert_equal actual_ports, expected_ports
5444
end
5545

test/sentinel_test.rb

Lines changed: 64 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,23 @@
1-
require_relative "helper"
1+
# frozen_string_literal: true
22

3-
class SentinelTest < Test::Unit::TestCase
3+
require_relative 'helper'
44

5-
include Helper::Client
5+
class SentinelTest < Test::Unit::TestCase
6+
include Helper::Sentinel
67

7-
def test_sentinel_connection
8-
sentinels = [{:host => "127.0.0.1", :port => 26381},
9-
{:host => "127.0.0.1", :port => 26382}]
8+
def test_sentinel_master_role_connection
9+
actual = redis.role
1010

11-
commands = {
12-
:s1 => [],
13-
:s2 => [],
14-
}
15-
16-
handler = lambda do |id|
17-
{
18-
:sentinel => lambda do |command, *args|
19-
commands[id] << [command, *args]
20-
["127.0.0.1", "6381"]
21-
end
22-
}
23-
end
24-
25-
RedisMock.start(handler.call(:s1)) do |s1_port|
26-
RedisMock.start(handler.call(:s2)) do |s2_port|
27-
sentinels[0][:port] = s1_port
28-
sentinels[1][:port] = s2_port
29-
redis = Redis.new(:url => "redis://master1", :sentinels => sentinels, :role => :master)
11+
assert_equal 'master', actual[0]
12+
assert_equal SLAVE_PORT, actual[2][0][1]
13+
end
3014

31-
assert redis.ping
32-
end
33-
end
15+
def test_sentinel_slave_role_connection
16+
redis = build_slave_role_client
17+
actual = redis.role
3418

35-
assert_equal commands[:s1], [%w[get-master-addr-by-name master1]]
36-
assert_equal commands[:s2], []
19+
assert_equal 'slave', actual[0]
20+
assert_equal MASTER_PORT.to_i, actual[2]
3721
end
3822

3923
def test_sentinel_failover
@@ -126,7 +110,7 @@ def test_sentinel_with_non_sentinel_options
126110
{
127111
:auth => lambda do |pass|
128112
commands[:s1] << ["auth", pass]
129-
"-ERR unknown command 'auth'"
113+
'+OK'
130114
end,
131115
:select => lambda do |db|
132116
commands[:s1] << ["select", db]
@@ -159,7 +143,55 @@ def test_sentinel_with_non_sentinel_options
159143
end
160144
end
161145

162-
assert_equal [%w[get-master-addr-by-name master1]], commands[:s1]
146+
assert_equal [%w[auth foo], %w[get-master-addr-by-name master1]], commands[:s1]
147+
assert_equal [%w[auth foo], %w[role]], commands[:m1]
148+
end
149+
150+
def test_sentinel_authentication_in_redis_prior_to_version_five
151+
sentinels = [{ host: '127.0.0.1', port: 26381 }]
152+
commands = { s1: [], m1: [] }
153+
154+
sentinel = lambda do |port|
155+
{
156+
auth: lambda do |pass|
157+
commands[:s1] << ['auth', pass]
158+
'-ERR unknown command `auth`'
159+
end,
160+
select: lambda do |db|
161+
commands[:s1] << ['select', db]
162+
'-ERR unknown command `select`'
163+
end,
164+
sentinel: lambda do |command, *args|
165+
commands[:s1] << [command, *args]
166+
['127.0.0.1', port.to_s]
167+
end
168+
}
169+
end
170+
171+
master = {
172+
auth: lambda do |pass|
173+
commands[:m1] << ['auth', pass]
174+
'+OK'
175+
end,
176+
role: lambda do
177+
commands[:m1] << ['role']
178+
['master']
179+
end
180+
}
181+
182+
RedisMock.start(master) do |master_port|
183+
RedisMock.start(sentinel.call(master_port)) do |sen_port|
184+
sentinels[0][:port] = sen_port
185+
redis = Redis.new(url: 'redis://master1',
186+
sentinels: sentinels,
187+
role: :master,
188+
password: 'foo')
189+
190+
assert redis.ping
191+
end
192+
end
193+
194+
assert_equal [%w[auth foo], %w[get-master-addr-by-name master1]], commands[:s1]
163195
assert_equal [%w[auth foo], %w[role]], commands[:m1]
164196
end
165197

0 commit comments

Comments
 (0)