redis 4.0.2 → 4.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.travis.yml +2 -2
  4. data/CHANGELOG.md +6 -0
  5. data/lib/redis.rb +97 -11
  6. data/lib/redis/client.rb +19 -11
  7. data/lib/redis/cluster.rb +285 -0
  8. data/lib/redis/cluster/command.rb +81 -0
  9. data/lib/redis/cluster/command_loader.rb +32 -0
  10. data/lib/redis/cluster/key_slot_converter.rb +72 -0
  11. data/lib/redis/cluster/node.rb +104 -0
  12. data/lib/redis/cluster/node_key.rb +35 -0
  13. data/lib/redis/cluster/node_loader.rb +35 -0
  14. data/lib/redis/cluster/option.rb +76 -0
  15. data/lib/redis/cluster/slot.rb +69 -0
  16. data/lib/redis/cluster/slot_loader.rb +47 -0
  17. data/lib/redis/errors.rb +46 -0
  18. data/lib/redis/version.rb +1 -1
  19. data/makefile +54 -16
  20. data/redis.gemspec +2 -1
  21. data/test/client_test.rb +17 -0
  22. data/test/cluster_abnormal_state_test.rb +38 -0
  23. data/test/cluster_blocking_commands_test.rb +15 -0
  24. data/test/cluster_client_internals_test.rb +77 -0
  25. data/test/cluster_client_key_hash_tags_test.rb +88 -0
  26. data/test/cluster_client_options_test.rb +147 -0
  27. data/test/cluster_client_pipelining_test.rb +59 -0
  28. data/test/cluster_client_replicas_test.rb +36 -0
  29. data/test/cluster_client_slots_test.rb +94 -0
  30. data/test/cluster_client_transactions_test.rb +71 -0
  31. data/test/cluster_commands_on_cluster_test.rb +165 -0
  32. data/test/cluster_commands_on_connection_test.rb +40 -0
  33. data/test/cluster_commands_on_geo_test.rb +74 -0
  34. data/test/cluster_commands_on_hashes_test.rb +11 -0
  35. data/test/cluster_commands_on_hyper_log_log_test.rb +17 -0
  36. data/test/cluster_commands_on_keys_test.rb +134 -0
  37. data/test/cluster_commands_on_lists_test.rb +15 -0
  38. data/test/cluster_commands_on_pub_sub_test.rb +101 -0
  39. data/test/cluster_commands_on_scripting_test.rb +56 -0
  40. data/test/cluster_commands_on_server_test.rb +221 -0
  41. data/test/cluster_commands_on_sets_test.rb +39 -0
  42. data/test/cluster_commands_on_sorted_sets_test.rb +35 -0
  43. data/test/cluster_commands_on_streams_test.rb +196 -0
  44. data/test/cluster_commands_on_strings_test.rb +15 -0
  45. data/test/cluster_commands_on_transactions_test.rb +41 -0
  46. data/test/cluster_commands_on_value_types_test.rb +14 -0
  47. data/test/commands_on_hashes_test.rb +2 -14
  48. data/test/commands_on_hyper_log_log_test.rb +2 -14
  49. data/test/commands_on_lists_test.rb +2 -13
  50. data/test/commands_on_sets_test.rb +2 -70
  51. data/test/commands_on_sorted_sets_test.rb +2 -145
  52. data/test/commands_on_strings_test.rb +2 -94
  53. data/test/distributed_blocking_commands_test.rb +8 -0
  54. data/test/distributed_commands_on_hashes_test.rb +16 -3
  55. data/test/distributed_commands_on_hyper_log_log_test.rb +8 -13
  56. data/test/distributed_commands_on_lists_test.rb +4 -5
  57. data/test/distributed_commands_on_sets_test.rb +45 -46
  58. data/test/distributed_commands_on_sorted_sets_test.rb +51 -8
  59. data/test/distributed_commands_on_strings_test.rb +10 -0
  60. data/test/helper.rb +176 -32
  61. data/test/internals_test.rb +13 -0
  62. data/test/lint/blocking_commands.rb +40 -16
  63. data/test/lint/hashes.rb +26 -0
  64. data/test/lint/hyper_log_log.rb +15 -1
  65. data/test/lint/lists.rb +16 -0
  66. data/test/lint/sets.rb +142 -0
  67. data/test/lint/sorted_sets.rb +183 -2
  68. data/test/lint/strings.rb +102 -0
  69. data/test/support/cluster/orchestrator.rb +199 -0
  70. metadata +79 -4
@@ -41,4 +41,12 @@ class TestDistributedBlockingCommands < Test::Unit::TestCase
41
41
  r.brpoplpush("foo", "bar", 0)
42
42
  end
43
43
  end
44
+
45
+ def test_bzpopmin
46
+ # Not implemented yet
47
+ end
48
+
49
+ def test_bzpopmax
50
+ # Not implemented yet
51
+ end
44
52
  end
@@ -1,8 +1,21 @@
1
- require_relative "helper"
2
- require_relative "lint/hashes"
1
+ require_relative 'helper'
2
+ require_relative 'lint/hashes'
3
3
 
4
4
  class TestDistributedCommandsOnHashes < Test::Unit::TestCase
5
-
6
5
  include Helper::Distributed
7
6
  include Lint::Hashes
7
+
8
+ def test_hscan
9
+ # Not implemented yet
10
+ end
11
+
12
+ def test_hstrlen
13
+ # Not implemented yet
14
+ end
15
+
16
+ def test_mapped_hmget_in_a_pipeline_returns_hash
17
+ assert_raise(Redis::Distributed::CannotDistribute) do
18
+ super
19
+ end
20
+ end
8
21
  end
@@ -1,31 +1,26 @@
1
- require_relative "helper"
2
- require_relative "lint/hyper_log_log"
1
+ require_relative 'helper'
2
+ require_relative 'lint/hyper_log_log'
3
3
 
4
4
  class TestDistributedCommandsOnHyperLogLog < Test::Unit::TestCase
5
-
6
5
  include Helper::Distributed
7
6
  include Lint::HyperLogLog
8
7
 
9
8
  def test_pfmerge
10
- target_version "2.8.9" do
9
+ target_version '2.8.9' do
11
10
  assert_raise Redis::Distributed::CannotDistribute do
12
- r.pfadd "foo", "s1"
13
- r.pfadd "bar", "s2"
14
-
15
- assert r.pfmerge("res", "foo", "bar")
11
+ super
16
12
  end
17
13
  end
18
14
  end
19
15
 
20
16
  def test_pfcount_multiple_keys_diff_nodes
21
- target_version "2.8.9" do
17
+ target_version '2.8.9' do
22
18
  assert_raise Redis::Distributed::CannotDistribute do
23
- r.pfadd "foo", "s1"
24
- r.pfadd "bar", "s2"
19
+ r.pfadd 'foo', 's1'
20
+ r.pfadd 'bar', 's2'
25
21
 
26
- assert r.pfcount("res", "foo", "bar")
22
+ assert r.pfcount('res', 'foo', 'bar')
27
23
  end
28
24
  end
29
25
  end
30
-
31
26
  end
@@ -1,20 +1,19 @@
1
- require_relative "helper"
2
- require_relative "lint/lists"
1
+ require_relative 'helper'
2
+ require_relative 'lint/lists'
3
3
 
4
4
  class TestDistributedCommandsOnLists < Test::Unit::TestCase
5
-
6
5
  include Helper::Distributed
7
6
  include Lint::Lists
8
7
 
9
8
  def test_rpoplpush
10
9
  assert_raise Redis::Distributed::CannotDistribute do
11
- r.rpoplpush("foo", "bar")
10
+ r.rpoplpush('foo', 'bar')
12
11
  end
13
12
  end
14
13
 
15
14
  def test_brpoplpush
16
15
  assert_raise Redis::Distributed::CannotDistribute do
17
- r.brpoplpush("foo", "bar", :timeout => 1)
16
+ r.brpoplpush('foo', 'bar', timeout: 1)
18
17
  end
19
18
  end
20
19
  end
@@ -1,106 +1,105 @@
1
- require_relative "helper"
2
- require_relative "lint/sets"
1
+ require_relative 'helper'
2
+ require_relative 'lint/sets'
3
3
 
4
4
  class TestDistributedCommandsOnSets < Test::Unit::TestCase
5
-
6
5
  include Helper::Distributed
7
6
  include Lint::Sets
8
7
 
9
8
  def test_smove
10
9
  assert_raise Redis::Distributed::CannotDistribute do
11
- r.sadd "foo", "s1"
12
- r.sadd "bar", "s2"
10
+ r.sadd 'foo', 's1'
11
+ r.sadd 'bar', 's2'
13
12
 
14
- r.smove("foo", "bar", "s1")
13
+ r.smove('foo', 'bar', 's1')
15
14
  end
16
15
  end
17
16
 
18
17
  def test_sinter
19
18
  assert_raise Redis::Distributed::CannotDistribute do
20
- r.sadd "foo", "s1"
21
- r.sadd "foo", "s2"
22
- r.sadd "bar", "s2"
19
+ r.sadd 'foo', 's1'
20
+ r.sadd 'foo', 's2'
21
+ r.sadd 'bar', 's2'
23
22
 
24
- r.sinter("foo", "bar")
23
+ r.sinter('foo', 'bar')
25
24
  end
26
25
  end
27
26
 
28
27
  def test_sinterstore
29
28
  assert_raise Redis::Distributed::CannotDistribute do
30
- r.sadd "foo", "s1"
31
- r.sadd "foo", "s2"
32
- r.sadd "bar", "s2"
29
+ r.sadd 'foo', 's1'
30
+ r.sadd 'foo', 's2'
31
+ r.sadd 'bar', 's2'
33
32
 
34
- r.sinterstore("baz", "foo", "bar")
33
+ r.sinterstore('baz', 'foo', 'bar')
35
34
  end
36
35
  end
37
36
 
38
37
  def test_sunion
39
38
  assert_raise Redis::Distributed::CannotDistribute do
40
- r.sadd "foo", "s1"
41
- r.sadd "foo", "s2"
42
- r.sadd "bar", "s2"
43
- r.sadd "bar", "s3"
39
+ r.sadd 'foo', 's1'
40
+ r.sadd 'foo', 's2'
41
+ r.sadd 'bar', 's2'
42
+ r.sadd 'bar', 's3'
44
43
 
45
- r.sunion("foo", "bar")
44
+ r.sunion('foo', 'bar')
46
45
  end
47
46
  end
48
47
 
49
48
  def test_sunionstore
50
49
  assert_raise Redis::Distributed::CannotDistribute do
51
- r.sadd "foo", "s1"
52
- r.sadd "foo", "s2"
53
- r.sadd "bar", "s2"
54
- r.sadd "bar", "s3"
50
+ r.sadd 'foo', 's1'
51
+ r.sadd 'foo', 's2'
52
+ r.sadd 'bar', 's2'
53
+ r.sadd 'bar', 's3'
55
54
 
56
- r.sunionstore("baz", "foo", "bar")
55
+ r.sunionstore('baz', 'foo', 'bar')
57
56
  end
58
57
  end
59
58
 
60
59
  def test_sdiff
61
60
  assert_raise Redis::Distributed::CannotDistribute do
62
- r.sadd "foo", "s1"
63
- r.sadd "foo", "s2"
64
- r.sadd "bar", "s2"
65
- r.sadd "bar", "s3"
61
+ r.sadd 'foo', 's1'
62
+ r.sadd 'foo', 's2'
63
+ r.sadd 'bar', 's2'
64
+ r.sadd 'bar', 's3'
66
65
 
67
- r.sdiff("foo", "bar")
66
+ r.sdiff('foo', 'bar')
68
67
  end
69
68
  end
70
69
 
71
70
  def test_sdiffstore
72
71
  assert_raise Redis::Distributed::CannotDistribute do
73
- r.sadd "foo", "s1"
74
- r.sadd "foo", "s2"
75
- r.sadd "bar", "s2"
76
- r.sadd "bar", "s3"
72
+ r.sadd 'foo', 's1'
73
+ r.sadd 'foo', 's2'
74
+ r.sadd 'bar', 's2'
75
+ r.sadd 'bar', 's3'
77
76
 
78
- r.sdiffstore("baz", "foo", "bar")
77
+ r.sdiffstore('baz', 'foo', 'bar')
79
78
  end
80
79
  end
81
80
 
82
81
  def test_sscan
83
82
  assert_nothing_raised do
84
- r.sadd "foo", "s1"
85
- r.sadd "foo", "s2"
86
- r.sadd "bar", "s2"
87
- r.sadd "bar", "s3"
83
+ r.sadd 'foo', 's1'
84
+ r.sadd 'foo', 's2'
85
+ r.sadd 'bar', 's2'
86
+ r.sadd 'bar', 's3'
88
87
 
89
- cursor, vals = r.sscan "foo", 0
88
+ cursor, vals = r.sscan 'foo', 0
90
89
  assert_equal '0', cursor
91
- assert_equal %w(s1 s2), vals.sort
90
+ assert_equal %w[s1 s2], vals.sort
92
91
  end
93
92
  end
94
93
 
95
94
  def test_sscan_each
96
95
  assert_nothing_raised do
97
- r.sadd "foo", "s1"
98
- r.sadd "foo", "s2"
99
- r.sadd "bar", "s2"
100
- r.sadd "bar", "s3"
96
+ r.sadd 'foo', 's1'
97
+ r.sadd 'foo', 's2'
98
+ r.sadd 'bar', 's2'
99
+ r.sadd 'bar', 's3'
101
100
 
102
- vals = r.sscan_each("foo").to_a
103
- assert_equal %w(s1 s2), vals.sort
101
+ vals = r.sscan_each('foo').to_a
102
+ assert_equal %w[s1 s2], vals.sort
104
103
  end
105
104
  end
106
105
  end
@@ -1,16 +1,59 @@
1
- require_relative "helper"
2
- require_relative "lint/sorted_sets"
1
+ require_relative 'helper'
2
+ require_relative 'lint/sorted_sets'
3
3
 
4
4
  class TestDistributedCommandsOnSortedSets < Test::Unit::TestCase
5
-
6
5
  include Helper::Distributed
7
6
  include Lint::SortedSets
8
7
 
9
- def test_zcount
10
- r.zadd "foo", 1, "s1"
11
- r.zadd "foo", 2, "s2"
12
- r.zadd "foo", 3, "s3"
8
+ def test_zinterstore
9
+ assert_raise(Redis::Distributed::CannotDistribute) { super }
10
+ end
11
+
12
+ def test_zinterstore_with_aggregate
13
+ assert_raise(Redis::Distributed::CannotDistribute) { super }
14
+ end
15
+
16
+ def test_zinterstore_with_weights
17
+ assert_raise(Redis::Distributed::CannotDistribute) { super }
18
+ end
19
+
20
+ def test_zlexcount
21
+ # Not implemented yet
22
+ end
23
+
24
+ def test_zpopmax
25
+ # Not implemented yet
26
+ end
27
+
28
+ def test_zpopmin
29
+ # Not implemented yet
30
+ end
31
+
32
+ def test_zrangebylex
33
+ # Not implemented yet
34
+ end
35
+
36
+ def test_zremrangebylex
37
+ # Not implemented yet
38
+ end
39
+
40
+ def test_zrevrangebylex
41
+ # Not implemented yet
42
+ end
43
+
44
+ def test_zscan
45
+ # Not implemented yet
46
+ end
47
+
48
+ def test_zunionstore
49
+ assert_raise(Redis::Distributed::CannotDistribute) { super }
50
+ end
51
+
52
+ def test_zunionstore_with_aggregate
53
+ assert_raise(Redis::Distributed::CannotDistribute) { super }
54
+ end
13
55
 
14
- assert_equal 2, r.zcount("foo", 2, 3)
56
+ def test_zunionstore_with_weights
57
+ assert_raise(Redis::Distributed::CannotDistribute) { super }
15
58
  end
16
59
  end
@@ -66,4 +66,14 @@ class TestDistributedCommandsOnStrings < Test::Unit::TestCase
66
66
  end
67
67
  end
68
68
  end
69
+
70
+ def test_mapped_mget_in_a_pipeline_returns_hash
71
+ assert_raise Redis::Distributed::CannotDistribute do
72
+ super
73
+ end
74
+ end
75
+
76
+ def test_bitfield
77
+ # Not implemented yet
78
+ end
69
79
  end
@@ -1,4 +1,5 @@
1
1
  require "test/unit"
2
+ require "mocha/test_unit"
2
3
  require "logger"
3
4
  require "stringio"
4
5
 
@@ -12,39 +13,12 @@ require_relative "../lib/redis/connection/#{ENV["DRIVER"]}"
12
13
 
13
14
  require_relative "support/redis_mock"
14
15
  require_relative "support/connection/#{ENV["DRIVER"]}"
16
+ require_relative 'support/cluster/orchestrator'
15
17
 
16
18
  PORT = 6381
17
19
  OPTIONS = {:port => PORT, :db => 15, :timeout => Float(ENV["TIMEOUT"] || 0.1)}
18
20
  NODES = ["redis://127.0.0.1:#{PORT}/15"]
19
21
 
20
- def init(redis)
21
- begin
22
- redis.select 14
23
- redis.flushdb
24
- redis.select 15
25
- redis.flushdb
26
- redis
27
- rescue Redis::CannotConnectError
28
- puts <<-EOS
29
-
30
- Cannot connect to Redis.
31
-
32
- Make sure Redis is running on localhost, port #{PORT}.
33
- This testing suite connects to the database 15.
34
-
35
- Try this once:
36
-
37
- $ make clean
38
-
39
- Then run the build again:
40
-
41
- $ make
42
-
43
- EOS
44
- exit 1
45
- end
46
- end
47
-
48
22
  def driver(*drivers, &blk)
49
23
  if drivers.map(&:to_s).include?(ENV["DRIVER"])
50
24
  class_eval(&blk)
@@ -133,6 +107,32 @@ module Helper
133
107
  @redis.quit if @redis
134
108
  end
135
109
 
110
+ def init(redis)
111
+ redis.select 14
112
+ redis.flushdb
113
+ redis.select 15
114
+ redis.flushdb
115
+ redis
116
+ rescue Redis::CannotConnectError
117
+ puts <<-MSG
118
+
119
+ Cannot connect to Redis.
120
+
121
+ Make sure Redis is running on localhost, port #{PORT}.
122
+ This testing suite connects to the database 15.
123
+
124
+ Try this once:
125
+
126
+ $ make clean
127
+
128
+ Then run the build again:
129
+
130
+ $ make
131
+
132
+ MSG
133
+ exit 1
134
+ end
135
+
136
136
  def redis_mock(commands, options = {}, &blk)
137
137
  RedisMock.start(commands, options) do |port|
138
138
  yield _new_client(options.merge(:port => port))
@@ -156,16 +156,16 @@ module Helper
156
156
  yield
157
157
  end
158
158
  end
159
+
160
+ def version
161
+ Version.new(redis.info['redis_version'])
162
+ end
159
163
  end
160
164
 
161
165
  module Client
162
166
 
163
167
  include Generic
164
168
 
165
- def version
166
- Version.new(redis.info["redis_version"])
167
- end
168
-
169
169
  private
170
170
 
171
171
  def _format_options(options)
@@ -198,4 +198,148 @@ module Helper
198
198
  Redis::Distributed.new(NODES, _format_options(options).merge(:driver => ENV["conn"]))
199
199
  end
200
200
  end
201
+
202
+ module Cluster
203
+ include Generic
204
+
205
+ DEFAULT_HOST = '127.0.0.1'
206
+ DEFAULT_PORTS = (7000..7005).freeze
207
+
208
+ ClusterSlotsRawReply = lambda { |host, port|
209
+ # @see https://redis.io/topics/protocol
210
+ <<-REPLY.delete(' ')
211
+ *1\r
212
+ *4\r
213
+ :0\r
214
+ :16383\r
215
+ *3\r
216
+ $#{host.size}\r
217
+ #{host}\r
218
+ :#{port}\r
219
+ $40\r
220
+ 649fa246273043021a05f547a79478597d3f1dc5\r
221
+ *3\r
222
+ $#{host.size}\r
223
+ #{host}\r
224
+ :#{port}\r
225
+ $40\r
226
+ 649fa246273043021a05f547a79478597d3f1dc5\r
227
+ REPLY
228
+ }
229
+
230
+ ClusterNodesRawReply = lambda { |host, port|
231
+ line = "649fa246273043021a05f547a79478597d3f1dc5 #{host}:#{port}@17000 "\
232
+ 'myself,master - 0 1530797742000 1 connected 0-16383'
233
+ "$#{line.size}\r\n#{line}\r\n"
234
+ }
235
+
236
+ def init(redis)
237
+ redis.flushall
238
+ redis
239
+ rescue Redis::CannotConnectError
240
+ puts <<-MSG
241
+
242
+ Cannot connect to Redis Cluster.
243
+
244
+ Make sure Redis is running on localhost, port #{DEFAULT_PORTS}.
245
+
246
+ Try this once:
247
+
248
+ $ make stop_cluster
249
+
250
+ Then run the build again:
251
+
252
+ $ make
253
+
254
+ MSG
255
+ exit 1
256
+ end
257
+
258
+ def build_another_client(options = {})
259
+ _new_client(options)
260
+ end
261
+
262
+ def redis_cluster_mock(commands, options = {})
263
+ host = DEFAULT_HOST
264
+ port = nil
265
+
266
+ cluster_subcommands = if commands.key?(:cluster)
267
+ commands.delete(:cluster)
268
+ .map { |k, v| [k.to_s.downcase, v] }
269
+ .to_h
270
+ else
271
+ {}
272
+ end
273
+
274
+ commands[:cluster] = lambda { |subcommand, *args|
275
+ if cluster_subcommands.key?(subcommand)
276
+ cluster_subcommands[subcommand].call(*args)
277
+ else
278
+ case subcommand
279
+ when 'slots' then ClusterSlotsRawReply.call(host, port)
280
+ when 'nodes' then ClusterNodesRawReply.call(host, port)
281
+ else '+OK'
282
+ end
283
+ end
284
+ }
285
+
286
+ commands[:command] = ->(*_) { "*0\r\n" }
287
+
288
+ RedisMock.start(commands, options) do |po|
289
+ port = po
290
+ scheme = options[:ssl] ? 'rediss' : 'redis'
291
+ nodes = %W[#{scheme}://#{host}:#{port}]
292
+ yield _new_client(options.merge(cluster: nodes))
293
+ end
294
+ end
295
+
296
+ def redis_cluster_down
297
+ trib = ClusterOrchestrator.new(_default_nodes)
298
+ trib.down
299
+ yield
300
+ ensure
301
+ trib.rebuild
302
+ trib.close
303
+ end
304
+
305
+ def redis_cluster_failover
306
+ trib = ClusterOrchestrator.new(_default_nodes)
307
+ trib.failover
308
+ yield
309
+ ensure
310
+ trib.rebuild
311
+ trib.close
312
+ end
313
+
314
+ # @param slot [Integer]
315
+ # @param src [String] <ip>:<port>
316
+ # @param dest [String] <ip>:<port>
317
+ def redis_cluster_resharding(slot, src:, dest:)
318
+ trib = ClusterOrchestrator.new(_default_nodes)
319
+ trib.start_resharding(slot, src, dest)
320
+ yield
321
+ trib.finish_resharding(slot, dest)
322
+ ensure
323
+ trib.rebuild
324
+ trib.close
325
+ end
326
+
327
+ private
328
+
329
+ def _default_nodes(host: DEFAULT_HOST, ports: DEFAULT_PORTS)
330
+ ports.map { |port| "redis://#{host}:#{port}" }
331
+ end
332
+
333
+ def _format_options(options)
334
+ {
335
+ timeout: OPTIONS[:timeout],
336
+ logger: ::Logger.new(@log),
337
+ cluster: _default_nodes
338
+ }.merge(options)
339
+ end
340
+
341
+ def _new_client(options = {})
342
+ Redis.new(_format_options(options).merge(driver: ENV['DRIVER']))
343
+ end
344
+ end
201
345
  end