Skip to content

Commit 7a6dabc

Browse files
committed
Fix cluster connecting option bug and resolve #888
1 parent 538034a commit 7a6dabc

File tree

4 files changed

+62
-31
lines changed

4 files changed

+62
-31
lines changed

lib/redis/cluster.rb

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,12 +112,11 @@ def fetch_cluster_info!(option)
112112
node = Node.new(option.per_node_key)
113113
available_slots = SlotLoader.load(node)
114114
node_flags = NodeLoader.load_flags(node)
115-
available_node_urls = NodeKey.to_node_urls(available_slots.keys, secure: option.secure?)
116-
option.update_node(available_node_urls)
115+
option.update_node(available_slots.keys.map { |k| NodeKey.optionize(k) })
117116
[Node.new(option.per_node_key, node_flags, option.use_replica?),
118117
Slot.new(available_slots, node_flags, option.use_replica?)]
119118
ensure
120-
node.map(&:disconnect)
119+
node&.map(&:disconnect)
121120
end
122121

123122
def fetch_command_details(nodes)

lib/redis/cluster/node_key.rb

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,13 @@ class Cluster
66
# It is different from node id.
77
# Node id is internal identifying code in Redis Cluster.
88
module NodeKey
9-
DEFAULT_SCHEME = 'redis'
10-
SECURE_SCHEME = 'rediss'
119
DELIMITER = ':'
1210

1311
module_function
1412

15-
def to_node_urls(node_keys, secure:)
16-
scheme = secure ? SECURE_SCHEME : DEFAULT_SCHEME
17-
node_keys
18-
.map { |k| k.split(DELIMITER) }
19-
.map { |k| URI::Generic.build(scheme: scheme, host: k[0], port: k[1].to_i).to_s }
13+
def optionize(node_key)
14+
host, port = split(node_key)
15+
{ host: host, port: port }
2016
end
2117

2218
def split(node_key)

lib/redis/cluster/option.rb

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,35 +15,33 @@ class Option
1515
def initialize(options)
1616
options = options.dup
1717
node_addrs = options.delete(:cluster)
18-
@node_uris = build_node_uris(node_addrs)
18+
@node_opts = build_node_options(node_addrs)
1919
@replica = options.delete(:replica) == true
20+
add_common_node_option_if_needed(options, @node_opts, :scheme)
21+
add_common_node_option_if_needed(options, @node_opts, :password)
2022
@options = options
2123
end
2224

2325
def per_node_key
24-
@node_uris.map { |uri| [NodeKey.build_from_uri(uri), @options.merge(url: uri.to_s)] }
26+
@node_opts.map { |opt| [NodeKey.build_from_host_port(opt[:host], opt[:port]), @options.merge(opt)] }
2527
.to_h
2628
end
2729

28-
def secure?
29-
@node_uris.any? { |uri| uri.scheme == SECURE_SCHEME } || @options[:ssl_params] || false
30-
end
31-
3230
def use_replica?
3331
@replica
3432
end
3533

3634
def update_node(addrs)
37-
@node_uris = build_node_uris(addrs)
35+
@node_opts = build_node_options(addrs)
3836
end
3937

4038
def add_node(host, port)
41-
@node_uris << parse_node_hash(host: host, port: port)
39+
@node_opts << { host: host, port: port }
4240
end
4341

4442
private
4543

46-
def build_node_uris(addrs)
44+
def build_node_options(addrs)
4745
raise InvalidClientOptionError, 'Redis option of `cluster` must be an Array' unless addrs.is_a?(Array)
4846
addrs.map { |addr| parse_node_addr(addr) }
4947
end
@@ -53,7 +51,7 @@ def parse_node_addr(addr)
5351
when String
5452
parse_node_url(addr)
5553
when Hash
56-
parse_node_hash(addr)
54+
parse_node_option(addr)
5755
else
5856
raise InvalidClientOptionError, 'Redis option of `cluster` must includes String or Hash'
5957
end
@@ -62,15 +60,27 @@ def parse_node_addr(addr)
6260
def parse_node_url(addr)
6361
uri = URI(addr)
6462
raise InvalidClientOptionError, "Invalid uri scheme #{addr}" unless VALID_SCHEMES.include?(uri.scheme)
65-
uri
63+
64+
db = uri.path.split('/')[1]&.to_i
65+
{ scheme: uri.scheme, password: uri.password, host: uri.host, port: uri.port, db: db }.compact
6666
rescue URI::InvalidURIError => err
6767
raise InvalidClientOptionError, err.message
6868
end
6969

70-
def parse_node_hash(addr)
70+
def parse_node_option(addr)
7171
addr = addr.map { |k, v| [k.to_sym, v] }.to_h
7272
raise InvalidClientOptionError, 'Redis option of `cluster` must includes `:host` and `:port` keys' if addr.values_at(:host, :port).any?(&:nil?)
73-
URI::Generic.build(scheme: DEFAULT_SCHEME, host: addr[:host], port: addr[:port].to_i)
73+
74+
addr
75+
end
76+
77+
# Redis cluster node returns only host and port information.
78+
# So we should complement additional information such as:
79+
# scheme, password and so on.
80+
def add_common_node_option_if_needed(options, node_opts, key)
81+
return options if options[key].nil? && node_opts.first[key].nil?
82+
83+
options[key] = options[key] || node_opts.first[key]
7484
end
7585
end
7686
end

test/cluster_client_options_test.rb

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,44 @@ class TestClusterClientOptions < Minitest::Test
77
include Helper::Cluster
88

99
def test_option_class
10-
option = Redis::Cluster::Option.new(cluster: %w[rediss://127.0.0.1:7000], replica: true)
11-
assert_equal({ '127.0.0.1:7000' => { url: 'rediss://127.0.0.1:7000' } }, option.per_node_key)
12-
assert_equal true, option.secure?
10+
option = Redis::Cluster::Option.new(cluster: %w[redis://127.0.0.1:7000], replica: true)
11+
assert_equal({ '127.0.0.1:7000' => { scheme: 'redis', host: '127.0.0.1', port: 7000 } }, option.per_node_key)
1312
assert_equal true, option.use_replica?
1413

1514
option = Redis::Cluster::Option.new(cluster: %w[redis://127.0.0.1:7000], replica: false)
16-
assert_equal({ '127.0.0.1:7000' => { url: 'redis://127.0.0.1:7000' } }, option.per_node_key)
17-
assert_equal false, option.secure?
15+
assert_equal({ '127.0.0.1:7000' => { scheme: 'redis', host: '127.0.0.1', port: 7000 } }, option.per_node_key)
1816
assert_equal false, option.use_replica?
1917

2018
option = Redis::Cluster::Option.new(cluster: %w[redis://127.0.0.1:7000])
21-
assert_equal({ '127.0.0.1:7000' => { url: 'redis://127.0.0.1:7000' } }, option.per_node_key)
22-
assert_equal false, option.secure?
19+
assert_equal({ '127.0.0.1:7000' => { scheme: 'redis', host: '127.0.0.1', port: 7000 } }, option.per_node_key)
2320
assert_equal false, option.use_replica?
21+
22+
option = Redis::Cluster::Option.new(cluster: %w[rediss://johndoe:[email protected]:7000/1/namespace])
23+
assert_equal({ '127.0.0.1:7000' => { scheme: 'rediss', password: 'foobar', host: '127.0.0.1', port: 7000, db: 1 } }, option.per_node_key)
24+
25+
option = Redis::Cluster::Option.new(cluster: %w[rediss://127.0.0.1:7000], scheme: 'redis')
26+
assert_equal({ '127.0.0.1:7000' => { scheme: 'rediss', host: '127.0.0.1', port: 7000 } }, option.per_node_key)
27+
28+
option = Redis::Cluster::Option.new(cluster: %w[redis://:[email protected]:7000], password: 'foobar')
29+
assert_equal({ '127.0.0.1:7000' => { scheme: 'redis', password: 'bazzap', host: '127.0.0.1', port: 7000 } }, option.per_node_key)
30+
31+
option = Redis::Cluster::Option.new(cluster: %w[redis://127.0.0.1:7000/0], db: 1)
32+
assert_equal({ '127.0.0.1:7000' => { scheme: 'redis', host: '127.0.0.1', port: 7000, db: 0 } }, option.per_node_key)
33+
34+
option = Redis::Cluster::Option.new(cluster: [{ host: '127.0.0.1', port: 7000 }])
35+
assert_equal({ '127.0.0.1:7000' => { host: '127.0.0.1', port: 7000 } }, option.per_node_key)
36+
37+
assert_raises(Redis::InvalidClientOptionError) do
38+
Redis::Cluster::Option.new(cluster: nil)
39+
end
40+
41+
assert_raises(Redis::InvalidClientOptionError) do
42+
Redis::Cluster::Option.new(cluster: %w[invalid_uri])
43+
end
44+
45+
assert_raises(Redis::InvalidClientOptionError) do
46+
Redis::Cluster::Option.new(cluster: [{ host: '127.0.0.1' }])
47+
end
2448
end
2549

2650
def test_client_accepts_valid_node_configs
@@ -43,7 +67,9 @@ def test_client_ignores_invalid_options
4367
end
4468

4569
def test_client_works_even_if_so_many_unavailable_nodes_specified
46-
nodes = (6001..7005).map { |port| "redis://127.0.0.1:#{port}" }
70+
min = 7000
71+
max = min + Process.getrlimit(Process::RLIMIT_NOFILE).first / 3 * 2
72+
nodes = (min..max).map { |port| "redis://127.0.0.1:#{port}" }
4773
redis = build_another_client(cluster: nodes)
4874

4975
assert_equal 'PONG', redis.ping

0 commit comments

Comments
 (0)