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
@@ -1,6 +1,9 @@
1
1
  module Lint
2
2
 
3
3
  module Strings
4
+ def mock(*args, &block)
5
+ redis_mock(*args, &block)
6
+ end
4
7
 
5
8
  def test_set_and_get
6
9
  r.set("foo", "s1")
@@ -242,5 +245,104 @@ module Lint
242
245
 
243
246
  assert_equal 5, r.strlen("foo")
244
247
  end
248
+
249
+ def test_bitfield
250
+ target_version('3.2.0') do
251
+ mock(bitfield: ->(*_) { "*2\r\n:1\r\n:0\r\n" }) do |redis|
252
+ assert_equal [1, 0], redis.bitfield('foo', 'INCRBY', 'i5', 100, 1, 'GET', 'u4', 0)
253
+ end
254
+ end
255
+ end
256
+
257
+ def test_mget
258
+ r.set('{1}foo', 's1')
259
+ r.set('{1}bar', 's2')
260
+
261
+ assert_equal %w[s1 s2], r.mget('{1}foo', '{1}bar')
262
+ assert_equal ['s1', 's2', nil], r.mget('{1}foo', '{1}bar', '{1}baz')
263
+ end
264
+
265
+ def test_mget_mapped
266
+ r.set('{1}foo', 's1')
267
+ r.set('{1}bar', 's2')
268
+
269
+ response = r.mapped_mget('{1}foo', '{1}bar')
270
+
271
+ assert_equal 's1', response['{1}foo']
272
+ assert_equal 's2', response['{1}bar']
273
+
274
+ response = r.mapped_mget('{1}foo', '{1}bar', '{1}baz')
275
+
276
+ assert_equal 's1', response['{1}foo']
277
+ assert_equal 's2', response['{1}bar']
278
+ assert_equal nil, response['{1}baz']
279
+ end
280
+
281
+ def test_mapped_mget_in_a_pipeline_returns_hash
282
+ r.set('{1}foo', 's1')
283
+ r.set('{1}bar', 's2')
284
+
285
+ result = r.pipelined do
286
+ r.mapped_mget('{1}foo', '{1}bar')
287
+ end
288
+
289
+ assert_equal({ '{1}foo' => 's1', '{1}bar' => 's2' }, result[0])
290
+ end
291
+
292
+ def test_mset
293
+ r.mset('{1}foo', 's1', '{1}bar', 's2')
294
+
295
+ assert_equal 's1', r.get('{1}foo')
296
+ assert_equal 's2', r.get('{1}bar')
297
+ end
298
+
299
+ def test_mset_mapped
300
+ r.mapped_mset('{1}foo' => 's1', '{1}bar' => 's2')
301
+
302
+ assert_equal 's1', r.get('{1}foo')
303
+ assert_equal 's2', r.get('{1}bar')
304
+ end
305
+
306
+ def test_msetnx
307
+ r.set('{1}foo', 's1')
308
+ assert_equal false, r.msetnx('{1}foo', 's2', '{1}bar', 's3')
309
+ assert_equal 's1', r.get('{1}foo')
310
+ assert_equal nil, r.get('{1}bar')
311
+
312
+ r.del('{1}foo')
313
+ assert_equal true, r.msetnx('{1}foo', 's2', '{1}bar', 's3')
314
+ assert_equal 's2', r.get('{1}foo')
315
+ assert_equal 's3', r.get('{1}bar')
316
+ end
317
+
318
+ def test_msetnx_mapped
319
+ r.set('{1}foo', 's1')
320
+ assert_equal false, r.mapped_msetnx('{1}foo' => 's2', '{1}bar' => 's3')
321
+ assert_equal 's1', r.get('{1}foo')
322
+ assert_equal nil, r.get('{1}bar')
323
+
324
+ r.del('{1}foo')
325
+ assert_equal true, r.mapped_msetnx('{1}foo' => 's2', '{1}bar' => 's3')
326
+ assert_equal 's2', r.get('{1}foo')
327
+ assert_equal 's3', r.get('{1}bar')
328
+ end
329
+
330
+ def test_bitop
331
+ with_external_encoding('UTF-8') do
332
+ target_version '2.5.10' do
333
+ r.set('foo{1}', 'a')
334
+ r.set('bar{1}', 'b')
335
+
336
+ r.bitop(:and, 'foo&bar{1}', 'foo{1}', 'bar{1}')
337
+ assert_equal "\x60", r.get('foo&bar{1}')
338
+ r.bitop(:or, 'foo|bar{1}', 'foo{1}', 'bar{1}')
339
+ assert_equal "\x63", r.get('foo|bar{1}')
340
+ r.bitop(:xor, 'foo^bar{1}', 'foo{1}', 'bar{1}')
341
+ assert_equal "\x03", r.get('foo^bar{1}')
342
+ r.bitop(:not, '~foo{1}', 'foo{1}')
343
+ assert_equal "\x9E", r.get('~foo{1}')
344
+ end
345
+ end
346
+ end
245
347
  end
246
348
  end
@@ -0,0 +1,199 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../lib/redis'
4
+
5
+ class ClusterOrchestrator
6
+ SLOT_SIZE = 16384
7
+
8
+ def initialize(node_addrs)
9
+ raise 'Redis Cluster requires at least 3 master nodes.' if node_addrs.size < 3
10
+ @clients = node_addrs.map { |addr| Redis.new(url: addr) }
11
+ end
12
+
13
+ def rebuild
14
+ flush_all_data(@clients)
15
+ reset_cluster(@clients)
16
+ assign_slots(@clients)
17
+ save_config_epoch(@clients)
18
+ meet_each_other(@clients)
19
+ wait_meeting(@clients)
20
+ replicate(@clients)
21
+ save_config(@clients)
22
+ wait_cluster_building(@clients)
23
+ sleep 3
24
+ end
25
+
26
+ def down
27
+ flush_all_data(@clients)
28
+ reset_cluster(@clients)
29
+ end
30
+
31
+ def failover
32
+ take_slaves(@clients).last.cluster(:failover, :takeover)
33
+ sleep 3
34
+ end
35
+
36
+ def start_resharding(slot, src_node_key, dest_node_key)
37
+ node_map = hashify_node_map(@clients.first)
38
+ src_node_id = node_map.fetch(src_node_key)
39
+ src_client = find_client(@clients, src_node_key)
40
+ dest_node_id = node_map.fetch(dest_node_key)
41
+ dest_client = find_client(@clients, dest_node_key)
42
+ dest_host, dest_port = dest_node_key.split(':')
43
+
44
+ dest_client.cluster(:setslot, slot, 'IMPORTING', src_node_id)
45
+ src_client.cluster(:setslot, slot, 'MIGRATING', dest_node_id)
46
+
47
+ loop do
48
+ keys = src_client.cluster(:getkeysinslot, slot, 100)
49
+ break if keys.empty?
50
+ keys.each { |k| src_client.migrate(k, host: dest_host, port: dest_port) }
51
+ sleep 0.1
52
+ end
53
+ end
54
+
55
+ def finish_resharding(slot, dest_node_key)
56
+ node_map = hashify_node_map(@clients.first)
57
+ @clients.first.cluster(:setslot, slot, 'NODE', node_map.fetch(dest_node_key))
58
+ end
59
+
60
+ def close
61
+ @clients.each(&:quit)
62
+ end
63
+
64
+ private
65
+
66
+ def flush_all_data(clients)
67
+ clients.each do |c|
68
+ begin
69
+ c.flushall
70
+ rescue Redis::CommandError
71
+ # READONLY You can't write against a read only slave.
72
+ nil
73
+ end
74
+ end
75
+ end
76
+
77
+ def reset_cluster(clients)
78
+ clients.each { |c| c.cluster(:reset) }
79
+ end
80
+
81
+ def assign_slots(clients)
82
+ masters = take_masters(clients)
83
+ slot_slice = SLOT_SIZE / masters.size
84
+ mod = SLOT_SIZE % masters.size
85
+ slot_sizes = Array.new(masters.size, slot_slice)
86
+ mod.downto(1) { |i| slot_sizes[i] += 1 }
87
+
88
+ slot_idx = 0
89
+ masters.zip(slot_sizes).each do |c, s|
90
+ slot_range = slot_idx..slot_idx + s - 1
91
+ c.cluster(:addslots, *slot_range.to_a)
92
+ slot_idx += s
93
+ end
94
+ end
95
+
96
+ def save_config_epoch(clients)
97
+ clients.each_with_index do |c, i|
98
+ begin
99
+ c.cluster('set-config-epoch', i + 1)
100
+ rescue Redis::CommandError
101
+ # ERR Node config epoch is already non-zero
102
+ nil
103
+ end
104
+ end
105
+ end
106
+
107
+ def meet_each_other(clients)
108
+ first_cliient = clients.first
109
+ target_info = first_cliient.connection
110
+ target_host = target_info.fetch(:host)
111
+ target_port = target_info.fetch(:port)
112
+
113
+ clients.each do |client|
114
+ next if first_cliient.id == client.id
115
+ client.cluster(:meet, target_host, target_port)
116
+ end
117
+ end
118
+
119
+ def wait_meeting(clients)
120
+ first_cliient = clients.first
121
+ size = clients.size
122
+
123
+ loop do
124
+ info = hashify_cluster_info(first_cliient)
125
+ break if info['cluster_known_nodes'].to_i == size
126
+ sleep 0.1
127
+ end
128
+ end
129
+
130
+ def replicate(clients)
131
+ node_map = hashify_node_map(clients.first)
132
+ masters = take_masters(clients)
133
+
134
+ take_slaves(clients).each_with_index do |slave, i|
135
+ master_info = masters[i].connection
136
+ master_host = master_info.fetch(:host)
137
+ master_port = master_info.fetch(:port)
138
+
139
+ loop do
140
+ begin
141
+ master_node_id = node_map.fetch("#{master_host}:#{master_port}")
142
+ slave.cluster(:replicate, master_node_id)
143
+ rescue Redis::CommandError
144
+ # ERR Unknown node [key]
145
+ sleep 0.1
146
+ node_map = hashify_node_map(clients.first)
147
+ next
148
+ end
149
+
150
+ break
151
+ end
152
+ end
153
+ end
154
+
155
+ def save_config(clients)
156
+ clients.each { |c| c.cluster(:saveconfig) }
157
+ end
158
+
159
+ def wait_cluster_building(clients)
160
+ first_cliient = clients.first
161
+
162
+ loop do
163
+ info = hashify_cluster_info(first_cliient)
164
+ break if info['cluster_state'] == 'ok'
165
+ sleep 0.1
166
+ end
167
+ end
168
+
169
+ def hashify_cluster_info(client)
170
+ client.cluster(:info).split("\r\n").map { |str| str.split(':') }.to_h
171
+ end
172
+
173
+ def hashify_node_map(client)
174
+ client.cluster(:nodes)
175
+ .split("\n")
176
+ .map { |str| str.split(' ') }
177
+ .map { |arr| [arr[1].split('@').first, arr[0]] }
178
+ .to_h
179
+ end
180
+
181
+ def take_masters(clients)
182
+ size = clients.size / 2
183
+ return clients if size < 3
184
+ clients.take(size)
185
+ end
186
+
187
+ def take_slaves(clients)
188
+ size = clients.size / 2
189
+ return [] if size < 3
190
+ clients[size..size * 2]
191
+ end
192
+
193
+ def find_client(clients, node_key)
194
+ clients.find do |cli|
195
+ con = cli.connection
196
+ node_key == "#{con.fetch(:host)}:#{con.fetch(:port)}"
197
+ end
198
+ end
199
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.2
4
+ version: 4.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ezra Zygmuntowicz
@@ -16,7 +16,7 @@ authors:
16
16
  autorequire:
17
17
  bindir: bin
18
18
  cert_chain: []
19
- date: 2018-08-13 00:00:00.000000000 Z
19
+ date: 2018-10-31 00:00:00.000000000 Z
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
22
22
  name: test-unit
@@ -32,6 +32,20 @@ dependencies:
32
32
  - - ">="
33
33
  - !ruby/object:Gem::Version
34
34
  version: 3.1.5
35
+ - !ruby/object:Gem::Dependency
36
+ name: mocha
37
+ requirement: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ type: :development
43
+ prerelease: false
44
+ version_requirements: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
35
49
  - !ruby/object:Gem::Dependency
36
50
  name: hiredis
37
51
  requirement: !ruby/object:Gem::Requirement
@@ -65,8 +79,7 @@ description: |2
65
79
  providing an idiomatic interface.
66
80
  email:
67
81
  - redis-db@googlegroups.com
68
- executables:
69
- - build
82
+ executables: []
70
83
  extensions: []
71
84
  extra_rdoc_files: []
72
85
  files:
@@ -99,6 +112,16 @@ files:
99
112
  - examples/unicorn/unicorn.rb
100
113
  - lib/redis.rb
101
114
  - lib/redis/client.rb
115
+ - lib/redis/cluster.rb
116
+ - lib/redis/cluster/command.rb
117
+ - lib/redis/cluster/command_loader.rb
118
+ - lib/redis/cluster/key_slot_converter.rb
119
+ - lib/redis/cluster/node.rb
120
+ - lib/redis/cluster/node_key.rb
121
+ - lib/redis/cluster/node_loader.rb
122
+ - lib/redis/cluster/option.rb
123
+ - lib/redis/cluster/slot.rb
124
+ - lib/redis/cluster/slot_loader.rb
102
125
  - lib/redis/connection.rb
103
126
  - lib/redis/connection/command_helper.rb
104
127
  - lib/redis/connection/hiredis.rb
@@ -116,6 +139,31 @@ files:
116
139
  - test/bitpos_test.rb
117
140
  - test/blocking_commands_test.rb
118
141
  - test/client_test.rb
142
+ - test/cluster_abnormal_state_test.rb
143
+ - test/cluster_blocking_commands_test.rb
144
+ - test/cluster_client_internals_test.rb
145
+ - test/cluster_client_key_hash_tags_test.rb
146
+ - test/cluster_client_options_test.rb
147
+ - test/cluster_client_pipelining_test.rb
148
+ - test/cluster_client_replicas_test.rb
149
+ - test/cluster_client_slots_test.rb
150
+ - test/cluster_client_transactions_test.rb
151
+ - test/cluster_commands_on_cluster_test.rb
152
+ - test/cluster_commands_on_connection_test.rb
153
+ - test/cluster_commands_on_geo_test.rb
154
+ - test/cluster_commands_on_hashes_test.rb
155
+ - test/cluster_commands_on_hyper_log_log_test.rb
156
+ - test/cluster_commands_on_keys_test.rb
157
+ - test/cluster_commands_on_lists_test.rb
158
+ - test/cluster_commands_on_pub_sub_test.rb
159
+ - test/cluster_commands_on_scripting_test.rb
160
+ - test/cluster_commands_on_server_test.rb
161
+ - test/cluster_commands_on_sets_test.rb
162
+ - test/cluster_commands_on_sorted_sets_test.rb
163
+ - test/cluster_commands_on_streams_test.rb
164
+ - test/cluster_commands_on_strings_test.rb
165
+ - test/cluster_commands_on_transactions_test.rb
166
+ - test/cluster_commands_on_value_types_test.rb
119
167
  - test/command_map_test.rb
120
168
  - test/commands_on_geo_test.rb
121
169
  - test/commands_on_hashes_test.rb
@@ -171,6 +219,7 @@ files:
171
219
  - test/sentinel_test.rb
172
220
  - test/sorting_test.rb
173
221
  - test/ssl_test.rb
222
+ - test/support/cluster/orchestrator.rb
174
223
  - test/support/connection/hiredis.rb
175
224
  - test/support/connection/ruby.rb
176
225
  - test/support/connection/synchrony.rb
@@ -220,6 +269,31 @@ test_files:
220
269
  - test/bitpos_test.rb
221
270
  - test/blocking_commands_test.rb
222
271
  - test/client_test.rb
272
+ - test/cluster_abnormal_state_test.rb
273
+ - test/cluster_blocking_commands_test.rb
274
+ - test/cluster_client_internals_test.rb
275
+ - test/cluster_client_key_hash_tags_test.rb
276
+ - test/cluster_client_options_test.rb
277
+ - test/cluster_client_pipelining_test.rb
278
+ - test/cluster_client_replicas_test.rb
279
+ - test/cluster_client_slots_test.rb
280
+ - test/cluster_client_transactions_test.rb
281
+ - test/cluster_commands_on_cluster_test.rb
282
+ - test/cluster_commands_on_connection_test.rb
283
+ - test/cluster_commands_on_geo_test.rb
284
+ - test/cluster_commands_on_hashes_test.rb
285
+ - test/cluster_commands_on_hyper_log_log_test.rb
286
+ - test/cluster_commands_on_keys_test.rb
287
+ - test/cluster_commands_on_lists_test.rb
288
+ - test/cluster_commands_on_pub_sub_test.rb
289
+ - test/cluster_commands_on_scripting_test.rb
290
+ - test/cluster_commands_on_server_test.rb
291
+ - test/cluster_commands_on_sets_test.rb
292
+ - test/cluster_commands_on_sorted_sets_test.rb
293
+ - test/cluster_commands_on_streams_test.rb
294
+ - test/cluster_commands_on_strings_test.rb
295
+ - test/cluster_commands_on_transactions_test.rb
296
+ - test/cluster_commands_on_value_types_test.rb
223
297
  - test/command_map_test.rb
224
298
  - test/commands_on_geo_test.rb
225
299
  - test/commands_on_hashes_test.rb
@@ -275,6 +349,7 @@ test_files:
275
349
  - test/sentinel_test.rb
276
350
  - test/sorting_test.rb
277
351
  - test/ssl_test.rb
352
+ - test/support/cluster/orchestrator.rb
278
353
  - test/support/connection/hiredis.rb
279
354
  - test/support/connection/ruby.rb
280
355
  - test/support/connection/synchrony.rb