redis 4.0.2 → 4.0.3

Sign up to get free protection for your applications and to get access to all the features.
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