This is an automated email from the ASF dual-hosted git repository. jensdeppe pushed a commit to branch support/1.15 in repository https://gitbox.apache.org/repos/asf/geode.git
commit ebd17de286f5e12945c239ad6dfc9fd658ed7d8b Author: Donal Evans <doev...@vmware.com> AuthorDate: Fri Jan 21 17:54:03 2022 -0800 GEODE-9922: Move Redis cross-slot checking to RegionProvider (#7295) * GEODE-9922: Move Redis cross-slot checking to RegionProvider - Move duplicated logic for determining if Keys are in different slots from various Executors to RegionProvider - Removed manual checks for if the key is local, as this is performed as part of locking the primary bucket - Created RedisCrossSlotException class - Added unit tests for new method in RegionProvider - Refactor SetOpExecutor to also lock the destination key for *STORE commands - Add missing test cases for cross-slot errors - Correct some tests for cross-slot behaviour that were inadvertantly testing the Jedis client's response rather than the Geode for Redis server - Changed name format for constants in AbstractSMoveIntegrationTest - Modify patch file to ensure tcl tests use keys with the same slot Authored-by: Donal Evans <doev...@vmware.com> (cherry picked from commit 7b0a88dbee36c6eb51513715af943f80ea6d93f9) --- .../resources/0001-configure-redis-tests.patch | 340 ++++++++++++++++++++- .../key/AbstractRenameIntegrationTest.java | 15 +- .../key/AbstractRenameNXIntegrationTest.java | 12 + .../executor/set/AbstractSDiffIntegrationTest.java | 15 +- .../set/AbstractSDiffStoreIntegrationTest.java | 15 +- .../set/AbstractSInterIntegrationTest.java | 14 +- .../executor/set/AbstractSMoveIntegrationTest.java | 165 +++++----- .../set/AbstractSUnionIntegrationTest.java | 10 +- .../set/AbstractSUnionStoreIntegrationTest.java | 8 +- .../string/AbstractMSetIntegrationTest.java | 3 +- .../string/AbstractMSetNXIntegrationTest.java | 3 +- .../apache/geode/codeAnalysis/excludedClasses.txt | 1 + .../geode/redis/internal/RedisConstants.java | 2 - .../executor/key/AbstractRenameExecutor.java | 5 - .../commands/executor/set/SMoveExecutor.java | 9 - .../commands/executor/set/SetOpExecutor.java | 22 +- .../executor/sortedset/ZInterStoreExecutor.java | 2 +- .../executor/sortedset/ZStoreExecutor.java | 14 +- .../executor/sortedset/ZUnionStoreExecutor.java | 2 +- .../executor/string/AbstractMSetExecutor.java | 8 +- .../internal/data/RedisCrossSlotException.java | 30 ++ .../internal/netty/ExecutionHandlerContext.java | 3 + .../redis/internal/services/RegionProvider.java | 20 +- .../internal/services/RegionProviderTest.java | 74 +++++ 24 files changed, 635 insertions(+), 157 deletions(-) diff --git a/geode-for-redis/src/acceptanceTest/resources/0001-configure-redis-tests.patch b/geode-for-redis/src/acceptanceTest/resources/0001-configure-redis-tests.patch index 88e1318..f9952bd 100644 --- a/geode-for-redis/src/acceptanceTest/resources/0001-configure-redis-tests.patch +++ b/geode-for-redis/src/acceptanceTest/resources/0001-configure-redis-tests.patch @@ -111,7 +111,7 @@ index f5da728e8..13985dce2 100644 test {Once AUTH succeeded we can actually send commands to the server} { diff --git a/tests/unit/dump.tcl b/tests/unit/dump.tcl -index 4c4e5d075..18bb694f2 100644 +index 4c4e5d075..e465300f4 100644 --- a/tests/unit/dump.tcl +++ b/tests/unit/dump.tcl @@ -41,34 +41,35 @@ start_server {tags {"dump"}} { @@ -162,7 +162,7 @@ index 4c4e5d075..18bb694f2 100644 +# assert {$idle >= 1000 && $idle <= 1010} +# r get foo +# } {bar} -+# ++# +# test {RESTORE can set LFU} { +# r set foo bar +# set encoded [r dump foo] @@ -1337,7 +1337,7 @@ index d2c679d32..6d17de48b 100644 # The following test can only be executed if we don't use Valgrind, and if # we are using x86_64 architecture, because: diff --git a/tests/unit/type/set.tcl b/tests/unit/type/set.tcl -index 7b467f1c4..21f0721c4 100644 +index 7b467f1c4..0c5ca1753 100644 --- a/tests/unit/type/set.tcl +++ b/tests/unit/type/set.tcl @@ -34,8 +34,8 @@ start_server { @@ -1360,15 +1360,341 @@ index 7b467f1c4..21f0721c4 100644 assert_encoding intset myintset assert_encoding hashtable mylargeintset assert_encoding hashtable myhashset -@@ -157,7 +157,7 @@ start_server { +@@ -113,19 +113,19 @@ start_server { + + foreach {type} {hashtable intset} { + for {set i 1} {$i <= 5} {incr i} { +- r del [format "set%d" $i] ++ r del [format "{tag}set%d" $i] + } + for {set i 0} {$i < 200} {incr i} { +- r sadd set1 $i +- r sadd set2 [expr $i+195] ++ r sadd "{tag}set1" $i ++ r sadd "{tag}set2" [expr $i+195] + } + foreach i {199 195 1000 2000} { +- r sadd set3 $i ++ r sadd "{tag}set3" $i + } + for {set i 5} {$i < 200} {incr i} { +- r sadd set4 $i ++ r sadd "{tag}set4" $i + } +- r sadd set5 0 ++ r sadd "{tag}set5" 0 + + # To make sure the sets are encoded as the type we are testing -- also + # when the VM is enabled and the values may be swapped in and out +@@ -137,87 +137,87 @@ start_server { + } + + for {set i 1} {$i <= 5} {incr i} { +- r sadd [format "set%d" $i] $large ++ r sadd [format "{tag}set%d" $i] $large + } + + test "Generated sets must be encoded as $type" { + for {set i 1} {$i <= 5} {incr i} { +- assert_encoding $type [format "set%d" $i] ++ assert_encoding $type [format "{tag}set%d" $i] + } + } + + test "SINTER with two sets - $type" { +- assert_equal [list 195 196 197 198 199 $large] [lsort [r sinter set1 set2]] ++ assert_equal [list 195 196 197 198 199 $large] [lsort [r sinter "{tag}set1" "{tag}set2"]] + } + + test "SINTERSTORE with two sets - $type" { +- r sinterstore setres set1 set2 +- assert_encoding $type setres +- assert_equal [list 195 196 197 198 199 $large] [lsort [r smembers setres]] ++ r sinterstore "{tag}setres" "{tag}set1" "{tag}set2" ++ assert_encoding $type "{tag}setres" ++ assert_equal [list 195 196 197 198 199 $large] [lsort [r smembers "{tag}setres"]] } test "SINTERSTORE with two sets, after a DEBUG RELOAD - $type" { - r debug reload +- r sinterstore setres set1 set2 +- assert_encoding $type setres +- assert_equal [list 195 196 197 198 199 $large] [lsort [r smembers setres]] + #r debug reload - r sinterstore setres set1 set2 - assert_encoding $type setres - assert_equal [list 195 196 197 198 199 $large] [lsort [r smembers setres]] ++ r sinterstore "{tag}setres" "{tag}set1" "{tag}set2" ++ assert_encoding $type "{tag}setres" ++ assert_equal [list 195 196 197 198 199 $large] [lsort [r smembers "{tag}setres"]] + } + + test "SUNION with two sets - $type" { +- set expected [lsort -uniq "[r smembers set1] [r smembers set2]"] +- assert_equal $expected [lsort [r sunion set1 set2]] ++ set expected [lsort -uniq "[r smembers "{tag}set1"] [r smembers "{tag}set2"]"] ++ assert_equal $expected [lsort [r sunion "{tag}set1" "{tag}set2"]] + } + + test "SUNIONSTORE with two sets - $type" { +- r sunionstore setres set1 set2 +- assert_encoding $type setres +- set expected [lsort -uniq "[r smembers set1] [r smembers set2]"] +- assert_equal $expected [lsort [r smembers setres]] ++ r sunionstore "{tag}setres" "{tag}set1" "{tag}set2" ++ assert_encoding $type "{tag}setres" ++ set expected [lsort -uniq "[r smembers "{tag}set1"] [r smembers "{tag}set2"]"] ++ assert_equal $expected [lsort [r smembers "{tag}setres"]] + } + + test "SINTER against three sets - $type" { +- assert_equal [list 195 199 $large] [lsort [r sinter set1 set2 set3]] ++ assert_equal [list 195 199 $large] [lsort [r sinter "{tag}set1" "{tag}set2" "{tag}set3"]] + } + + test "SINTERSTORE with three sets - $type" { +- r sinterstore setres set1 set2 set3 +- assert_equal [list 195 199 $large] [lsort [r smembers setres]] ++ r sinterstore "{tag}setres" "{tag}set1" "{tag}set2" "{tag}set3" ++ assert_equal [list 195 199 $large] [lsort [r smembers "{tag}setres"]] + } + + test "SUNION with non existing keys - $type" { +- set expected [lsort -uniq "[r smembers set1] [r smembers set2]"] +- assert_equal $expected [lsort [r sunion nokey1 set1 set2 nokey2]] ++ set expected [lsort -uniq "[r smembers "{tag}set1"] [r smembers "{tag}set2"]"] ++ assert_equal $expected [lsort [r sunion "{tag}nokey1" "{tag}set1" "{tag}set2" "{tag}nokey2"]] + } + + test "SDIFF with two sets - $type" { +- assert_equal {0 1 2 3 4} [lsort [r sdiff set1 set4]] ++ assert_equal {0 1 2 3 4} [lsort [r sdiff "{tag}set1" "{tag}set4"]] + } + + test "SDIFF with three sets - $type" { +- assert_equal {1 2 3 4} [lsort [r sdiff set1 set4 set5]] ++ assert_equal {1 2 3 4} [lsort [r sdiff "{tag}set1" "{tag}set4" "{tag}set5"]] + } + + test "SDIFFSTORE with three sets - $type" { +- r sdiffstore setres set1 set4 set5 ++ r sdiffstore "{tag}setres" "{tag}set1" "{tag}set4" "{tag}set5" + # When we start with intsets, we should always end with intsets. + if {$type eq {intset}} { +- assert_encoding intset setres ++ assert_encoding intset "{tag}setres" + } +- assert_equal {1 2 3 4} [lsort [r smembers setres]] ++ assert_equal {1 2 3 4} [lsort [r smembers "{tag}setres"]] + } + } + + test "SDIFF with first set empty" { +- r del set1 set2 set3 +- r sadd set2 1 2 3 4 +- r sadd set3 a b c d +- r sdiff set1 set2 set3 ++ r del "{tag}set1" "{tag}set2" "{tag}set3" ++ r sadd "{tag}set2" 1 2 3 4 ++ r sadd "{tag}set3" a b c d ++ r sdiff "{tag}set1" "{tag}set2" "{tag}set3" + } {} + + test "SDIFF with same set two times" { +- r del set1 +- r sadd set1 a b c 1 2 3 4 5 6 +- r sdiff set1 set1 ++ r del "{tag}set1" ++ r sadd "{tag}set1" a b c 1 2 3 4 5 6 ++ r sdiff "{tag}set1" "{tag}set1" + } {} + + test "SDIFF fuzzing" { +@@ -228,11 +228,11 @@ start_server { + set num_sets [expr {[randomInt 10]+1}] + for {set i 0} {$i < $num_sets} {incr i} { + set num_elements [randomInt 100] +- r del set_$i +- lappend args set_$i ++ r del [format "{tag}set%d" $i] ++ lappend args [format "{tag}set%d" $i] + while {$num_elements} { + set ele [randomValue] +- r sadd set_$i $ele ++ r sadd [format "{tag}set%d" $i] $ele + if {$i == 0} { + set s($ele) x + } else { +@@ -247,42 +247,42 @@ start_server { + } + + test "SINTER against non-set should throw error" { +- r set key1 x +- assert_error "WRONGTYPE*" {r sinter key1 noset} ++ r set "{tag}key1" x ++ assert_error "WRONGTYPE*" {r sinter "{tag}key1" "{tag}noset"} + } + + test "SUNION against non-set should throw error" { +- r set key1 x +- assert_error "WRONGTYPE*" {r sunion key1 noset} ++ r set "{tag}key1" x ++ assert_error "WRONGTYPE*" {r sunion "{tag}key1" "{tag}noset"} + } + + test "SINTER should handle non existing key as empty" { +- r del set1 set2 set3 +- r sadd set1 a b c +- r sadd set2 b c d +- r sinter set1 set2 set3 ++ r del "{tag}set1" "{tag}set2" "{tag}set3" ++ r sadd "{tag}set1" a b c ++ r sadd "{tag}set2" b c d ++ r sinter "{tag}set1" "{tag}set2" "{tag}set3" + } {} + + test "SINTER with same integer elements but different encoding" { +- r del set1 set2 +- r sadd set1 1 2 3 +- r sadd set2 1 2 3 a +- r srem set2 a +- assert_encoding intset set1 +- assert_encoding hashtable set2 +- lsort [r sinter set1 set2] ++ r del "{tag}set1" "{tag}set2" ++ r sadd "{tag}set1" 1 2 3 ++ r sadd "{tag}set2" 1 2 3 a ++ r srem "{tag}set2" a ++ assert_encoding intset "{tag}set1" ++ assert_encoding hashtable "{tag}set2" ++ lsort [r sinter "{tag}set1" "{tag}set2"] + } {1 2 3} + + test "SINTERSTORE against non existing keys should delete dstkey" { +- r set setres xxx +- assert_equal 0 [r sinterstore setres foo111 bar222] +- assert_equal 0 [r exists setres] ++ r set "{tag}setres" xxx ++ assert_equal 0 [r sinterstore "{tag}setres" "{tag}foo111" "{tag}bar222"] ++ assert_equal 0 [r exists "{tag}setres"] + } + + test "SUNIONSTORE against non existing keys should delete dstkey" { +- r set setres xxx +- assert_equal 0 [r sunionstore setres foo111 bar222] +- assert_equal 0 [r exists setres] ++ r set "{tag}setres" xxx ++ assert_equal 0 [r sunionstore "{tag}setres" "{tag}foo111" "{tag}bar222"] ++ assert_equal 0 [r exists "{tag}setres"] + } + + foreach {type contents} {hashtable {a b c} intset {1 2 3}} { +@@ -486,74 +486,74 @@ start_server { + } + + proc setup_move {} { +- r del myset3 myset4 +- create_set myset1 {1 a b} +- create_set myset2 {2 3 4} +- assert_encoding hashtable myset1 +- assert_encoding intset myset2 ++ r del "{tag}myset3" "{tag}myset4" ++ create_set "{tag}myset1" {1 a b} ++ create_set "{tag}myset2" {2 3 4} ++ assert_encoding hashtable "{tag}myset1" ++ assert_encoding intset "{tag}myset2" + } + + test "SMOVE basics - from regular set to intset" { + # move a non-integer element to an intset should convert encoding + setup_move +- assert_equal 1 [r smove myset1 myset2 a] +- assert_equal {1 b} [lsort [r smembers myset1]] +- assert_equal {2 3 4 a} [lsort [r smembers myset2]] +- assert_encoding hashtable myset2 ++ assert_equal 1 [r smove "{tag}myset1" "{tag}myset2" a] ++ assert_equal {1 b} [lsort [r smembers "{tag}myset1"]] ++ assert_equal {2 3 4 a} [lsort [r smembers "{tag}myset2"]] ++ assert_encoding hashtable "{tag}myset2" + + # move an integer element should not convert the encoding + setup_move +- assert_equal 1 [r smove myset1 myset2 1] +- assert_equal {a b} [lsort [r smembers myset1]] +- assert_equal {1 2 3 4} [lsort [r smembers myset2]] +- assert_encoding intset myset2 ++ assert_equal 1 [r smove "{tag}myset1" "{tag}myset2" 1] ++ assert_equal {a b} [lsort [r smembers "{tag}myset1"]] ++ assert_equal {1 2 3 4} [lsort [r smembers "{tag}myset2"]] ++ assert_encoding intset "{tag}myset2" + } + + test "SMOVE basics - from intset to regular set" { + setup_move +- assert_equal 1 [r smove myset2 myset1 2] +- assert_equal {1 2 a b} [lsort [r smembers myset1]] +- assert_equal {3 4} [lsort [r smembers myset2]] ++ assert_equal 1 [r smove "{tag}myset2" "{tag}myset1" 2] ++ assert_equal {1 2 a b} [lsort [r smembers "{tag}myset1"]] ++ assert_equal {3 4} [lsort [r smembers "{tag}myset2"]] + } + + test "SMOVE non existing key" { + setup_move +- assert_equal 0 [r smove myset1 myset2 foo] +- assert_equal 0 [r smove myset1 myset1 foo] +- assert_equal {1 a b} [lsort [r smembers myset1]] +- assert_equal {2 3 4} [lsort [r smembers myset2]] ++ assert_equal 0 [r smove "{tag}myset1" "{tag}myset2" foo] ++ assert_equal 0 [r smove "{tag}myset1" "{tag}myset1" foo] ++ assert_equal {1 a b} [lsort [r smembers "{tag}myset1"]] ++ assert_equal {2 3 4} [lsort [r smembers "{tag}myset2"]] + } + + test "SMOVE non existing src set" { + setup_move +- assert_equal 0 [r smove noset myset2 foo] +- assert_equal {2 3 4} [lsort [r smembers myset2]] ++ assert_equal 0 [r smove "{tag}noset" "{tag}myset2" foo] ++ assert_equal {2 3 4} [lsort [r smembers "{tag}myset2"]] + } + + test "SMOVE from regular set to non existing destination set" { + setup_move +- assert_equal 1 [r smove myset1 myset3 a] +- assert_equal {1 b} [lsort [r smembers myset1]] +- assert_equal {a} [lsort [r smembers myset3]] +- assert_encoding hashtable myset3 ++ assert_equal 1 [r smove "{tag}myset1" "{tag}myset3" a] ++ assert_equal {1 b} [lsort [r smembers "{tag}myset1"]] ++ assert_equal {a} [lsort [r smembers "{tag}myset3"]] ++ assert_encoding hashtable "{tag}myset3" + } + + test "SMOVE from intset to non existing destination set" { + setup_move +- assert_equal 1 [r smove myset2 myset3 2] +- assert_equal {3 4} [lsort [r smembers myset2]] +- assert_equal {2} [lsort [r smembers myset3]] +- assert_encoding intset myset3 ++ assert_equal 1 [r smove "{tag}myset2" "{tag}myset3" 2] ++ assert_equal {3 4} [lsort [r smembers "{tag}myset2"]] ++ assert_equal {2} [lsort [r smembers "{tag}myset3"]] ++ assert_encoding intset "{tag}myset3" + } + + test "SMOVE wrong src key type" { +- r set x 10 +- assert_error "WRONGTYPE*" {r smove x myset2 foo} ++ r set "{tag}x" 10 ++ assert_error "WRONGTYPE*" {r smove "{tag}x" "{tag}myset2" foo} + } + + test "SMOVE wrong dst key type" { +- r set x 10 +- assert_error "WRONGTYPE*" {r smove myset2 x foo} ++ r set "{tag}x" 10 ++ assert_error "WRONGTYPE*" {r smove "{tag}myset2" "{tag}x" foo} + } + + test "SMOVE with identical source and destination" { diff --git a/tests/unit/type/string.tcl b/tests/unit/type/string.tcl index 7122fd987..2274c82cc 100644 --- a/tests/unit/type/string.tcl diff --git a/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/key/AbstractRenameIntegrationTest.java b/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/key/AbstractRenameIntegrationTest.java index 1feb6fb..a2af1b8 100644 --- a/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/key/AbstractRenameIntegrationTest.java +++ b/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/key/AbstractRenameIntegrationTest.java @@ -17,10 +17,12 @@ package org.apache.geode.redis.internal.commands.executor.key; import static org.apache.geode.redis.RedisCommandArgumentsTestHelper.assertExactNumberOfArgs; import static org.apache.geode.redis.internal.RedisConstants.ERROR_NO_SUCH_KEY; +import static org.apache.geode.redis.internal.RedisConstants.ERROR_WRONG_SLOT; import static org.apache.geode.test.dunit.rules.RedisClusterStartupRule.REDIS_CLIENT_TIMEOUT; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import static org.junit.Assert.fail; +import static redis.clients.jedis.Protocol.Command.RENAME; import java.util.ArrayList; import java.util.Arrays; @@ -40,7 +42,6 @@ import org.junit.Before; import org.junit.Test; import redis.clients.jedis.HostAndPort; import redis.clients.jedis.JedisCluster; -import redis.clients.jedis.Protocol; import org.apache.geode.redis.ConcurrentLoopingThreads; import org.apache.geode.redis.RedisIntegrationTest; @@ -67,7 +68,17 @@ public abstract class AbstractRenameIntegrationTest implements RedisIntegrationT @Test public void errors_GivenWrongNumberOfArguments() { - assertExactNumberOfArgs(jedis, Protocol.Command.RENAME, 2); + assertExactNumberOfArgs(jedis, RENAME, 2); + } + + @Test + public void shouldReturnCrossSlotError_givenKeysInDifferentSlots() { + String key1 = "{tag1}key1"; + String key2 = "{tag2}key2"; + jedis.set(key1, "value1"); + jedis.set(key2, "value1"); + assertThatThrownBy(() -> jedis.sendCommand(key1, RENAME, key1, key2)) + .hasMessageContaining("CROSSSLOT " + ERROR_WRONG_SLOT); } @Test diff --git a/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/key/AbstractRenameNXIntegrationTest.java b/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/key/AbstractRenameNXIntegrationTest.java index 954cebb..04b27ac 100644 --- a/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/key/AbstractRenameNXIntegrationTest.java +++ b/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/key/AbstractRenameNXIntegrationTest.java @@ -17,10 +17,12 @@ package org.apache.geode.redis.internal.commands.executor.key; import static org.apache.geode.redis.RedisCommandArgumentsTestHelper.assertExactNumberOfArgs; import static org.apache.geode.redis.internal.RedisConstants.ERROR_NO_SUCH_KEY; +import static org.apache.geode.redis.internal.RedisConstants.ERROR_WRONG_SLOT; import static org.apache.geode.test.dunit.rules.RedisClusterStartupRule.REDIS_CLIENT_TIMEOUT; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import static org.junit.Assert.fail; +import static redis.clients.jedis.Protocol.Command.RENAMENX; import java.util.ArrayList; import java.util.Arrays; @@ -71,6 +73,16 @@ public abstract class AbstractRenameNXIntegrationTest implements RedisIntegratio } @Test + public void shouldReturnCrossSlotError_givenKeysInDifferentSlots() { + String key1 = "{tag1}key1"; + String key2 = "{tag2}key2"; + jedis.set(key1, "value1"); + jedis.set(key2, "value1"); + assertThatThrownBy(() -> jedis.sendCommand(key1, RENAMENX, key1, key2)) + .hasMessageContaining("CROSSSLOT " + ERROR_WRONG_SLOT); + } + + @Test public void shouldRename_givenNewKey() { jedis.set("{tag1}foo", "bar"); long result = jedis.renamenx("{tag1}foo", "{tag1}newfoo"); diff --git a/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSDiffIntegrationTest.java b/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSDiffIntegrationTest.java index 3b4ec8c..7b7f7e3 100755 --- a/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSDiffIntegrationTest.java +++ b/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSDiffIntegrationTest.java @@ -15,11 +15,13 @@ package org.apache.geode.redis.internal.commands.executor.set; import static org.apache.geode.redis.RedisCommandArgumentsTestHelper.assertAtLeastNArgs; +import static org.apache.geode.redis.internal.RedisConstants.ERROR_WRONG_SLOT; import static org.apache.geode.redis.internal.RedisConstants.ERROR_WRONG_TYPE; import static org.apache.geode.test.dunit.rules.RedisClusterStartupRule.BIND_ADDRESS; import static org.apache.geode.test.dunit.rules.RedisClusterStartupRule.REDIS_CLIENT_TIMEOUT; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static redis.clients.jedis.Protocol.Command.SDIFF; import java.util.Arrays; import java.util.HashSet; @@ -31,7 +33,6 @@ import org.junit.Before; import org.junit.Test; import redis.clients.jedis.HostAndPort; import redis.clients.jedis.JedisCluster; -import redis.clients.jedis.Protocol; import org.apache.geode.redis.ConcurrentLoopingThreads; import org.apache.geode.redis.RedisIntegrationTest; @@ -54,7 +55,7 @@ public abstract class AbstractSDiffIntegrationTest implements RedisIntegrationTe @Test public void sdiffErrors_givenTooFewArguments() { - assertAtLeastNArgs(jedis, Protocol.Command.SDIFF, 1); + assertAtLeastNArgs(jedis, SDIFF, 1); } @Test @@ -64,6 +65,16 @@ public abstract class AbstractSDiffIntegrationTest implements RedisIntegrationTe } @Test + public void sdif_withSetsFromDifferentSlots_returnsCrossSlotError() { + String setKeyDifferentSlot = "{tag2}set2"; + jedis.sadd(SET_KEY, "member1"); + jedis.sadd(setKeyDifferentSlot, "member2"); + + assertThatThrownBy(() -> jedis.sendCommand(SET_KEY, SDIFF, SET_KEY, setKeyDifferentSlot)) + .hasMessage("CROSSSLOT " + ERROR_WRONG_SLOT); + } + + @Test public void sdiffWithNonExistentSet_returnsEmptySet() { assertThat(jedis.sdiff(NON_EXISTENT_SET_KEY)).isEmpty(); } diff --git a/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSDiffStoreIntegrationTest.java b/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSDiffStoreIntegrationTest.java index e34c3dd..c60fe6e 100755 --- a/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSDiffStoreIntegrationTest.java +++ b/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSDiffStoreIntegrationTest.java @@ -15,11 +15,13 @@ package org.apache.geode.redis.internal.commands.executor.set; import static org.apache.geode.redis.RedisCommandArgumentsTestHelper.assertAtLeastNArgs; +import static org.apache.geode.redis.internal.RedisConstants.ERROR_WRONG_SLOT; import static org.apache.geode.redis.internal.RedisConstants.ERROR_WRONG_TYPE; import static org.apache.geode.test.dunit.rules.RedisClusterStartupRule.BIND_ADDRESS; import static org.apache.geode.test.dunit.rules.RedisClusterStartupRule.REDIS_CLIENT_TIMEOUT; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static redis.clients.jedis.Protocol.Command.SDIFFSTORE; import java.util.concurrent.atomic.AtomicLong; @@ -28,7 +30,6 @@ import org.junit.Before; import org.junit.Test; import redis.clients.jedis.HostAndPort; import redis.clients.jedis.JedisCluster; -import redis.clients.jedis.Protocol; import org.apache.geode.redis.ConcurrentLoopingThreads; import org.apache.geode.redis.RedisIntegrationTest; @@ -56,7 +57,17 @@ public abstract class AbstractSDiffStoreIntegrationTest implements RedisIntegrat @Test public void sdiffstoreTooFewArguments_returnsError() { - assertAtLeastNArgs(jedis, Protocol.Command.SDIFFSTORE, 2); + assertAtLeastNArgs(jedis, SDIFFSTORE, 2); + } + + @Test + public void sdifstore_withSetsFromDifferentSlots_returnsCrossSlotError() { + String setKeyDifferentSlot = "{tag2}set2"; + jedis.sadd(SET_KEY, "member1"); + jedis.sadd(setKeyDifferentSlot, "member2"); + + assertThatThrownBy(() -> jedis.sendCommand(SET_KEY, SDIFFSTORE, SET_KEY, setKeyDifferentSlot)) + .hasMessage("CROSSSLOT " + ERROR_WRONG_SLOT); } @Test diff --git a/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSInterIntegrationTest.java b/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSInterIntegrationTest.java index 168c24f..115317d 100755 --- a/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSInterIntegrationTest.java +++ b/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSInterIntegrationTest.java @@ -15,9 +15,11 @@ package org.apache.geode.redis.internal.commands.executor.set; import static org.apache.geode.redis.RedisCommandArgumentsTestHelper.assertAtLeastNArgs; +import static org.apache.geode.redis.internal.RedisConstants.ERROR_WRONG_SLOT; import static org.apache.geode.redis.internal.RedisConstants.ERROR_WRONG_TYPE; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static redis.clients.jedis.Protocol.Command.SINTER; import java.util.ArrayList; import java.util.HashSet; @@ -57,7 +59,7 @@ public abstract class AbstractSInterIntegrationTest implements RedisIntegrationT @Test public void sinterErrors_givenTooFewArguments() { - assertAtLeastNArgs(jedis, Protocol.Command.SINTER, 1); + assertAtLeastNArgs(jedis, SINTER, 1); } @Test @@ -66,6 +68,16 @@ public abstract class AbstractSInterIntegrationTest implements RedisIntegrationT } @Test + public void sinter_withSetsFromDifferentSlots_returnsCrossSlotError() { + String setKeyDifferentSlot = "{tag2}set2"; + jedis.sadd(SET1, "member1"); + jedis.sadd(setKeyDifferentSlot, "member2"); + + assertThatThrownBy(() -> jedis.sendCommand(SET1, SINTER, SET1, setKeyDifferentSlot)) + .hasMessage("CROSSSLOT " + ERROR_WRONG_SLOT); + } + + @Test public void testSInter_givenIntersection_returnsIntersectedMembers() { String[] firstSet = new String[] {"peach"}; String[] secondSet = new String[] {"linux", "apple", "peach"}; diff --git a/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSMoveIntegrationTest.java b/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSMoveIntegrationTest.java index 2ca43e3..ec01096 100755 --- a/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSMoveIntegrationTest.java +++ b/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSMoveIntegrationTest.java @@ -15,11 +15,12 @@ package org.apache.geode.redis.internal.commands.executor.set; import static org.apache.geode.redis.RedisCommandArgumentsTestHelper.assertExactNumberOfArgs; -import static org.apache.geode.redis.internal.RedisConstants.ERROR_DIFFERENT_SLOTS; +import static org.apache.geode.redis.internal.RedisConstants.ERROR_WRONG_SLOT; import static org.apache.geode.test.dunit.rules.RedisClusterStartupRule.BIND_ADDRESS; import static org.apache.geode.test.dunit.rules.RedisClusterStartupRule.REDIS_CLIENT_TIMEOUT; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static redis.clients.jedis.Protocol.Command.SMOVE; import java.util.concurrent.atomic.AtomicLong; @@ -29,7 +30,6 @@ import org.junit.Before; import org.junit.Test; import redis.clients.jedis.HostAndPort; import redis.clients.jedis.JedisCluster; -import redis.clients.jedis.Protocol; import org.apache.geode.redis.ConcurrentLoopingThreads; import org.apache.geode.redis.RedisIntegrationTest; @@ -37,12 +37,12 @@ import org.apache.geode.redis.internal.RedisConstants; public abstract class AbstractSMoveIntegrationTest implements RedisIntegrationTest { private JedisCluster jedis; - private static final String nonExistentSetKey = "{tag1}nonExistentSet"; - private static final String sourceKey = "{tag1}sourceKey"; - private static final String[] sourceMembers = {"one", "two", "three", "four", "five"}; - private static final String destKey = "{tag1}destKey"; - private static final String[] destMembers = {"a", "b", "c"}; - private static final String movedMember = "one"; + private static final String NON_EXISTENT_SET_KEY = "{tag1}nonExistentSet"; + private static final String SOURCE_KEY = "{tag1}sourceKey"; + private static final String[] SOURCE_MEMBERS = {"one", "two", "three", "four", "five"}; + private static final String DESTINATION_KEY = "{tag1}destKey"; + private static final String[] DESTINATION_MEMBERS = {"a", "b", "c"}; + private static final String MOVED_MEMBER = "one"; @Before public void setUp() { @@ -57,182 +57,187 @@ public abstract class AbstractSMoveIntegrationTest implements RedisIntegrationTe @Test public void smove_givenWrongNumberOfArguments_returnsError() { - assertExactNumberOfArgs(jedis, Protocol.Command.SMOVE, 3); + assertExactNumberOfArgs(jedis, SMOVE, 3); } @Test public void smove_withWrongTypeSource_returnsWrongTypeError() { - jedis.set(sourceKey, "value"); - jedis.sadd(destKey, destMembers); + jedis.set(SOURCE_KEY, "value"); + jedis.sadd(DESTINATION_KEY, DESTINATION_MEMBERS); - assertThatThrownBy(() -> jedis.smove(sourceKey, destKey, movedMember)) + assertThatThrownBy(() -> jedis.smove(SOURCE_KEY, DESTINATION_KEY, MOVED_MEMBER)) .hasMessageContaining(RedisConstants.ERROR_WRONG_TYPE); } @Test public void smove_withWrongTypeDest_returnsWrongTypeError() { - jedis.sadd(sourceKey, sourceMembers); - jedis.set(destKey, "value"); + jedis.sadd(SOURCE_KEY, SOURCE_MEMBERS); + jedis.set(DESTINATION_KEY, "value"); - assertThatThrownBy(() -> jedis.smove(sourceKey, destKey, movedMember)) + assertThatThrownBy(() -> jedis.smove(SOURCE_KEY, DESTINATION_KEY, MOVED_MEMBER)) .hasMessageContaining(RedisConstants.ERROR_WRONG_TYPE); } @Test public void smove_withWrongTypeSourceAndDest_returnsWrongTypeError() { - jedis.set(sourceKey, "sourceMember"); - jedis.set(destKey, "destMember"); + jedis.set(SOURCE_KEY, "sourceMember"); + jedis.set(DESTINATION_KEY, "destMember"); - assertThatThrownBy(() -> jedis.smove(sourceKey, destKey, movedMember)) + assertThatThrownBy(() -> jedis.smove(SOURCE_KEY, DESTINATION_KEY, MOVED_MEMBER)) .hasMessageContaining(RedisConstants.ERROR_WRONG_TYPE); } @Test public void smove_withNonExistentSource_returnsZero_sourceKeyDoesNotExist() { - jedis.sadd(destKey, destMembers); + jedis.sadd(DESTINATION_KEY, DESTINATION_MEMBERS); - assertThat(jedis.smove(nonExistentSetKey, destKey, movedMember)) + assertThat(jedis.smove(NON_EXISTENT_SET_KEY, DESTINATION_KEY, MOVED_MEMBER)) .isEqualTo(0); - assertThat(jedis.exists(nonExistentSetKey)).isFalse(); + assertThat(jedis.exists(NON_EXISTENT_SET_KEY)).isFalse(); } @Test public void smove_withNonExistentMemberInSource_returnsZero_memberNotAddedToDest() { String nonExistentMember = "foo"; - jedis.sadd(sourceKey, sourceMembers); - jedis.sadd(destKey, destMembers); + jedis.sadd(SOURCE_KEY, SOURCE_MEMBERS); + jedis.sadd(DESTINATION_KEY, DESTINATION_MEMBERS); - assertThat(jedis.smove(nonExistentSetKey, destKey, nonExistentMember)) + assertThat(jedis.smove(NON_EXISTENT_SET_KEY, DESTINATION_KEY, nonExistentMember)) .isEqualTo(0); - assertThat(jedis.sismember(destKey, nonExistentMember)).isFalse(); + assertThat(jedis.sismember(DESTINATION_KEY, nonExistentMember)).isFalse(); } @Test public void smove_withExistentSourceAndNonExistentDest_returnsOne_memberMovedFromSourceToCreatedDest() { - jedis.sadd(sourceKey, sourceMembers); + jedis.sadd(SOURCE_KEY, SOURCE_MEMBERS); - String[] sourceResult = ArrayUtils.remove(sourceMembers, 0); - String[] destResult = new String[] {movedMember}; + String[] sourceResult = ArrayUtils.remove(SOURCE_MEMBERS, 0); + String[] destResult = new String[] {MOVED_MEMBER}; - assertThat(jedis.smove(sourceKey, destKey, movedMember)) + assertThat(jedis.smove(SOURCE_KEY, DESTINATION_KEY, MOVED_MEMBER)) .isEqualTo(1); - assertThat(jedis.smembers(sourceKey)).containsExactlyInAnyOrder(sourceResult); - assertThat(jedis.smembers(destKey)).containsExactlyInAnyOrder(destResult); + assertThat(jedis.smembers(SOURCE_KEY)).containsExactlyInAnyOrder(sourceResult); + assertThat(jedis.smembers(DESTINATION_KEY)).containsExactlyInAnyOrder(destResult); } @Test public void smove_withExistentSourceAndDest_returnsOne_memberMovedFromSourceToDest() { - jedis.sadd(sourceKey, sourceMembers); - jedis.sadd(destKey, destMembers); + jedis.sadd(SOURCE_KEY, SOURCE_MEMBERS); + jedis.sadd(DESTINATION_KEY, DESTINATION_MEMBERS); - String[] sourceResult = ArrayUtils.remove(sourceMembers, 0); - String[] destResult = ArrayUtils.add(destMembers, movedMember); + String[] sourceResult = ArrayUtils.remove(SOURCE_MEMBERS, 0); + String[] destResult = ArrayUtils.add(DESTINATION_MEMBERS, MOVED_MEMBER); - assertThat(jedis.smove(sourceKey, destKey, movedMember)) + assertThat(jedis.smove(SOURCE_KEY, DESTINATION_KEY, MOVED_MEMBER)) .isEqualTo(1); - assertThat(jedis.smembers(sourceKey)).containsExactlyInAnyOrder(sourceResult); - assertThat(jedis.smembers(destKey)).containsExactlyInAnyOrder(destResult); + assertThat(jedis.smembers(SOURCE_KEY)).containsExactlyInAnyOrder(sourceResult); + assertThat(jedis.smembers(DESTINATION_KEY)).containsExactlyInAnyOrder(destResult); } @Test public void smove_withSameSourceAndDest_withMemberInDest_returnsOne_setNotModified() { - jedis.sadd(sourceKey, sourceMembers); - assertThat(jedis.smove(sourceKey, sourceKey, movedMember)) + jedis.sadd(SOURCE_KEY, SOURCE_MEMBERS); + assertThat(jedis.smove(SOURCE_KEY, SOURCE_KEY, MOVED_MEMBER)) .isEqualTo(1); - assertThat(jedis.smembers(sourceKey)).containsExactlyInAnyOrder(sourceMembers); + assertThat(jedis.smembers(SOURCE_KEY)).containsExactlyInAnyOrder(SOURCE_MEMBERS); } @Test public void smove_withExistentSourceAndDest_withMemberInDest_returnsOne_memberRemovedFromSource() { - jedis.sadd(sourceKey, sourceMembers); - String[] newDestMembers = ArrayUtils.add(destMembers, movedMember); - jedis.sadd(destKey, newDestMembers); + jedis.sadd(SOURCE_KEY, SOURCE_MEMBERS); + String[] newDestMembers = ArrayUtils.add(DESTINATION_MEMBERS, MOVED_MEMBER); + jedis.sadd(DESTINATION_KEY, newDestMembers); - String[] sourceResult = ArrayUtils.remove(sourceMembers, 0); + String[] sourceResult = ArrayUtils.remove(SOURCE_MEMBERS, 0); - assertThat(jedis.smove(sourceKey, destKey, movedMember)) + assertThat(jedis.smove(SOURCE_KEY, DESTINATION_KEY, MOVED_MEMBER)) .isEqualTo(1); - assertThat(jedis.smembers(sourceKey)).containsExactlyInAnyOrder(sourceResult); - assertThat(jedis.smembers(destKey)).containsExactlyInAnyOrder(newDestMembers); + assertThat(jedis.smembers(SOURCE_KEY)).containsExactlyInAnyOrder(sourceResult); + assertThat(jedis.smembers(DESTINATION_KEY)).containsExactlyInAnyOrder(newDestMembers); } @Test public void smoveWithSetsFromDifferentSlots_returnsCrossSlotError() { String setKeyDifferentSlot = "{tag2}setKey2"; - jedis.sadd(sourceKey, setKeyDifferentSlot); - jedis.sadd(sourceKey, sourceMembers); - jedis.sadd(setKeyDifferentSlot, destMembers); + jedis.sadd(SOURCE_KEY, setKeyDifferentSlot); + jedis.sadd(SOURCE_KEY, SOURCE_MEMBERS); + jedis.sadd(setKeyDifferentSlot, DESTINATION_MEMBERS); - assertThatThrownBy(() -> jedis.smove(sourceKey, setKeyDifferentSlot, movedMember)) - .hasMessageContaining(ERROR_DIFFERENT_SLOTS); + assertThatThrownBy( + () -> jedis.sendCommand(SOURCE_KEY, SMOVE, SOURCE_KEY, setKeyDifferentSlot, MOVED_MEMBER)) + .hasMessage("CROSSSLOT " + ERROR_WRONG_SLOT); } @Test public void ensureSetConsistency_whenRunningConcurrently_withSRemAndSMove() { - String[] sourceMemberRemoved = ArrayUtils.remove(sourceMembers, 0); - String[] destMemberAdded = ArrayUtils.add(destMembers, movedMember); + String[] sourceMemberRemoved = ArrayUtils.remove(SOURCE_MEMBERS, 0); + String[] destMemberAdded = ArrayUtils.add(DESTINATION_MEMBERS, MOVED_MEMBER); - jedis.sadd(sourceKey, sourceMembers); - jedis.sadd(destKey, destMembers); + jedis.sadd(SOURCE_KEY, SOURCE_MEMBERS); + jedis.sadd(DESTINATION_KEY, DESTINATION_MEMBERS); final AtomicLong moved = new AtomicLong(0); new ConcurrentLoopingThreads(1000, - i -> jedis.srem(sourceKey, movedMember), - i -> moved.set(jedis.smove(sourceKey, destKey, movedMember))) + i -> jedis.srem(SOURCE_KEY, MOVED_MEMBER), + i -> moved.set(jedis.smove(SOURCE_KEY, DESTINATION_KEY, MOVED_MEMBER))) .runWithAction(() -> { if (moved.get() == 1) { - assertThat(jedis.smembers(sourceKey)) + assertThat(jedis.smembers(SOURCE_KEY)) .containsExactlyInAnyOrder(sourceMemberRemoved); - assertThat(jedis.smembers(destKey)).containsExactlyInAnyOrder(destMemberAdded); + assertThat(jedis.smembers(DESTINATION_KEY)) + .containsExactlyInAnyOrder(destMemberAdded); } else { - assertThat(jedis.smembers(sourceKey)) + assertThat(jedis.smembers(SOURCE_KEY)) .containsExactlyInAnyOrder(sourceMemberRemoved); - assertThat(jedis.smembers(destKey)).containsExactlyInAnyOrder(destMembers); + assertThat(jedis.smembers(DESTINATION_KEY)).containsExactlyInAnyOrder( + DESTINATION_MEMBERS); } - jedis.sadd(sourceKey, movedMember); - jedis.srem(destKey, movedMember); + jedis.sadd(SOURCE_KEY, MOVED_MEMBER); + jedis.srem(DESTINATION_KEY, MOVED_MEMBER); }); } @Test public void ensureSetConsistency_whenRunningConcurrently_withSMovesFromSameSourceAndDifferentDestination() { - String[] sourceMemberRemoved = ArrayUtils.remove(sourceMembers, 0); - String[] destMemberAdded = ArrayUtils.add(destMembers, movedMember); - String[] nonExisistentMemberAdded = {movedMember}; + String[] sourceMemberRemoved = ArrayUtils.remove(SOURCE_MEMBERS, 0); + String[] destMemberAdded = ArrayUtils.add(DESTINATION_MEMBERS, MOVED_MEMBER); + String[] nonExisistentMemberAdded = {MOVED_MEMBER}; - jedis.sadd(sourceKey, sourceMembers); - jedis.sadd(destKey, destMembers); + jedis.sadd(SOURCE_KEY, SOURCE_MEMBERS); + jedis.sadd(DESTINATION_KEY, DESTINATION_MEMBERS); final AtomicLong movedToNonExistent = new AtomicLong(0); final AtomicLong movedToDest = new AtomicLong(0); new ConcurrentLoopingThreads(1000, - i -> movedToNonExistent.set(jedis.smove(sourceKey, nonExistentSetKey, movedMember)), - i -> movedToDest.set(jedis.smove(sourceKey, destKey, movedMember))) + i -> movedToNonExistent.set(jedis.smove(SOURCE_KEY, NON_EXISTENT_SET_KEY, MOVED_MEMBER)), + i -> movedToDest.set(jedis.smove(SOURCE_KEY, DESTINATION_KEY, MOVED_MEMBER))) .runWithAction(() -> { // Asserts that only one smove was preformed assertThat(movedToNonExistent.get() ^ movedToDest.get()).isEqualTo(1); - assertThat(jedis.smembers(sourceKey)).containsExactlyInAnyOrder(sourceMemberRemoved); + assertThat(jedis.smembers(SOURCE_KEY)).containsExactlyInAnyOrder(sourceMemberRemoved); if (movedToNonExistent.get() == 1) { - assertThat(jedis.smembers(nonExistentSetKey)) + assertThat(jedis.smembers(NON_EXISTENT_SET_KEY)) .containsExactlyInAnyOrder(nonExisistentMemberAdded); } else { - assertThat(jedis.exists(nonExistentSetKey)).isFalse(); + assertThat(jedis.exists(NON_EXISTENT_SET_KEY)).isFalse(); } if (movedToDest.get() == 1) { - assertThat(jedis.smembers(destKey)).containsExactlyInAnyOrder(destMemberAdded); + assertThat(jedis.smembers(DESTINATION_KEY)) + .containsExactlyInAnyOrder(destMemberAdded); } else { - assertThat(jedis.smembers(destKey)).containsExactlyInAnyOrder(destMembers); + assertThat(jedis.smembers(DESTINATION_KEY)).containsExactlyInAnyOrder( + DESTINATION_MEMBERS); } - jedis.sadd(sourceKey, movedMember); - jedis.srem(destKey, movedMember); - jedis.srem(nonExistentSetKey, movedMember); + jedis.sadd(SOURCE_KEY, MOVED_MEMBER); + jedis.srem(DESTINATION_KEY, MOVED_MEMBER); + jedis.srem(NON_EXISTENT_SET_KEY, MOVED_MEMBER); }); } } diff --git a/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSUnionIntegrationTest.java b/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSUnionIntegrationTest.java index 7e6ac15..40f0314 100755 --- a/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSUnionIntegrationTest.java +++ b/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSUnionIntegrationTest.java @@ -15,12 +15,13 @@ package org.apache.geode.redis.internal.commands.executor.set; import static org.apache.geode.redis.RedisCommandArgumentsTestHelper.assertAtLeastNArgs; -import static org.apache.geode.redis.internal.RedisConstants.ERROR_DIFFERENT_SLOTS; +import static org.apache.geode.redis.internal.RedisConstants.ERROR_WRONG_SLOT; import static org.apache.geode.redis.internal.RedisConstants.ERROR_WRONG_TYPE; import static org.apache.geode.test.dunit.rules.RedisClusterStartupRule.BIND_ADDRESS; import static org.apache.geode.test.dunit.rules.RedisClusterStartupRule.REDIS_CLIENT_TIMEOUT; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static redis.clients.jedis.Protocol.Command.SUNION; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; @@ -30,7 +31,6 @@ import org.junit.Before; import org.junit.Test; import redis.clients.jedis.HostAndPort; import redis.clients.jedis.JedisCluster; -import redis.clients.jedis.Protocol; import org.apache.geode.redis.ConcurrentLoopingThreads; import org.apache.geode.redis.RedisIntegrationTest; @@ -55,7 +55,7 @@ public abstract class AbstractSUnionIntegrationTest implements RedisIntegrationT @Test public void sunionErrors_givenTooFewArguments() { - assertAtLeastNArgs(jedis, Protocol.Command.SUNION, 1); + assertAtLeastNArgs(jedis, SUNION, 1); } @Test @@ -126,8 +126,8 @@ public abstract class AbstractSUnionIntegrationTest implements RedisIntegrationT jedis.sadd(SET_KEY_1, SET_MEMBERS_1); jedis.sadd(setKeyDifferentSlot, secondSetMembers); - assertThatThrownBy(() -> jedis.sunion(SET_KEY_1, setKeyDifferentSlot)) - .hasMessageContaining(ERROR_DIFFERENT_SLOTS); + assertThatThrownBy(() -> jedis.sendCommand(SET_KEY_1, SUNION, SET_KEY_1, setKeyDifferentSlot)) + .hasMessage("CROSSSLOT " + ERROR_WRONG_SLOT); } diff --git a/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSUnionStoreIntegrationTest.java b/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSUnionStoreIntegrationTest.java index 7d0ee9b..b6526c1 100644 --- a/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSUnionStoreIntegrationTest.java +++ b/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSUnionStoreIntegrationTest.java @@ -15,12 +15,13 @@ package org.apache.geode.redis.internal.commands.executor.set; import static org.apache.geode.redis.RedisCommandArgumentsTestHelper.assertAtLeastNArgs; -import static org.apache.geode.redis.internal.RedisConstants.ERROR_DIFFERENT_SLOTS; +import static org.apache.geode.redis.internal.RedisConstants.ERROR_WRONG_SLOT; import static org.apache.geode.redis.internal.RedisConstants.ERROR_WRONG_TYPE; import static org.apache.geode.test.dunit.rules.RedisClusterStartupRule.BIND_ADDRESS; import static org.apache.geode.test.dunit.rules.RedisClusterStartupRule.REDIS_CLIENT_TIMEOUT; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static redis.clients.jedis.Protocol.Command.SUNIONSTORE; import java.util.concurrent.atomic.AtomicLong; @@ -206,8 +207,9 @@ public abstract class AbstractSUnionStoreIntegrationTest implements RedisIntegra jedis.sadd(SET_KEY_1, SET_MEMBERS_1); jedis.sadd(setKeyDifferentSlot, secondSetMembers); - assertThatThrownBy(() -> jedis.sunionstore(DESTINATION_KEY, SET_KEY_1, setKeyDifferentSlot)) - .hasMessageContaining(ERROR_DIFFERENT_SLOTS); + assertThatThrownBy(() -> jedis.sendCommand(DESTINATION_KEY, SUNIONSTORE, DESTINATION_KEY, + SET_KEY_1, setKeyDifferentSlot)) + .hasMessage("CROSSSLOT " + ERROR_WRONG_SLOT); } @Test diff --git a/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/string/AbstractMSetIntegrationTest.java b/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/string/AbstractMSetIntegrationTest.java index 806bbad..65462f3 100755 --- a/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/string/AbstractMSetIntegrationTest.java +++ b/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/string/AbstractMSetIntegrationTest.java @@ -14,6 +14,7 @@ */ package org.apache.geode.redis.internal.commands.executor.string; +import static org.apache.geode.redis.internal.RedisConstants.ERROR_WRONG_SLOT; import static org.apache.geode.test.dunit.rules.RedisClusterStartupRule.BIND_ADDRESS; import static org.apache.geode.test.dunit.rules.RedisClusterStartupRule.REDIS_CLIENT_TIMEOUT; import static org.assertj.core.api.Assertions.assertThat; @@ -71,7 +72,7 @@ public abstract class AbstractMSetIntegrationTest implements RedisIntegrationTes public void givenDifferentSlots_returnsError() { assertThatThrownBy( () -> jedis.sendCommand("key1", Protocol.Command.MSET, "key1", "value1", "key2", "value2")) - .hasMessageContaining("CROSSSLOT Keys in request don't hash to the same slot"); + .hasMessageContaining("CROSSSLOT " + ERROR_WRONG_SLOT); } @Test diff --git a/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/string/AbstractMSetNXIntegrationTest.java b/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/string/AbstractMSetNXIntegrationTest.java index 2aa30d4..96f7ae5 100755 --- a/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/string/AbstractMSetNXIntegrationTest.java +++ b/geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/string/AbstractMSetNXIntegrationTest.java @@ -14,6 +14,7 @@ */ package org.apache.geode.redis.internal.commands.executor.string; +import static org.apache.geode.redis.internal.RedisConstants.ERROR_WRONG_SLOT; import static org.apache.geode.test.dunit.rules.RedisClusterStartupRule.BIND_ADDRESS; import static org.apache.geode.test.dunit.rules.RedisClusterStartupRule.REDIS_CLIENT_TIMEOUT; import static org.assertj.core.api.Assertions.assertThat; @@ -73,7 +74,7 @@ public abstract class AbstractMSetNXIntegrationTest implements RedisIntegrationT assertThatThrownBy( () -> jedis.sendCommand("key1", Protocol.Command.MSETNX, "key1", "value1", "key2", "value2")) - .hasMessageContaining("CROSSSLOT Keys in request don't hash to the same slot"); + .hasMessageContaining("CROSSSLOT " + ERROR_WRONG_SLOT); } @Test diff --git a/geode-for-redis/src/integrationTest/resources/org/apache/geode/codeAnalysis/excludedClasses.txt b/geode-for-redis/src/integrationTest/resources/org/apache/geode/codeAnalysis/excludedClasses.txt index b0aec9c..3d87db1 100644 --- a/geode-for-redis/src/integrationTest/resources/org/apache/geode/codeAnalysis/excludedClasses.txt +++ b/geode-for-redis/src/integrationTest/resources/org/apache/geode/codeAnalysis/excludedClasses.txt @@ -2,6 +2,7 @@ org/apache/geode/redis/internal/services/cluster/RedisMemberInfoRetrievalFunctio org/apache/geode/redis/internal/data/collections/Bytes2ObjectOpenHashMap org/apache/geode/redis/internal/data/collections/SizeableBytes2ObjectOpenCustomHashMapWithCursor org/apache/geode/redis/internal/data/collections/SizeableObjectOpenCustomHashSet +org/apache/geode/redis/internal/data/RedisCrossSlotException org/apache/geode/redis/internal/data/RedisDataMovedException org/apache/geode/redis/internal/data/RedisDataTypeMismatchException org/apache/geode/redis/internal/commands/executor/sortedset/ZAggregator diff --git a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/RedisConstants.java b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/RedisConstants.java index 25894a3..3a0d4fe 100644 --- a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/RedisConstants.java +++ b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/RedisConstants.java @@ -78,8 +78,6 @@ public class RedisConstants { "DUMP payload version or checksum are wrong"; public static final String ERROR_WRONG_SLOT = "Keys in request don't hash to the same slot"; - public static final String ERROR_DIFFERENT_SLOTS = - "No way to dispatch this command to Redis Cluster because keys have different slots."; public static final String ERROR_WEIGHT_NOT_A_FLOAT = "weight value is not a float"; public static final String ERROR_INVALID_USERNAME_OR_PASSWORD = diff --git a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/executor/key/AbstractRenameExecutor.java b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/executor/key/AbstractRenameExecutor.java index 62c5a47..d022e20 100644 --- a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/executor/key/AbstractRenameExecutor.java +++ b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/executor/key/AbstractRenameExecutor.java @@ -16,7 +16,6 @@ package org.apache.geode.redis.internal.commands.executor.key; import static org.apache.geode.redis.internal.RedisConstants.ERROR_NO_SUCH_KEY; -import static org.apache.geode.redis.internal.RedisConstants.ERROR_WRONG_SLOT; import java.util.Arrays; import java.util.List; @@ -41,10 +40,6 @@ public abstract class AbstractRenameExecutor implements CommandExecutor { return getTargetSameAsSourceResponse(); } - if (key.getSlot() != newKey.getSlot()) { - return RedisResponse.crossSlot(ERROR_WRONG_SLOT); - } - try { if (!executeRenameCommand(key, newKey, context)) { return getNoSuchKeyResponse(); diff --git a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/executor/set/SMoveExecutor.java b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/executor/set/SMoveExecutor.java index 2a9b4e4..42647ba 100755 --- a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/executor/set/SMoveExecutor.java +++ b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/executor/set/SMoveExecutor.java @@ -14,7 +14,6 @@ */ package org.apache.geode.redis.internal.commands.executor.set; -import static org.apache.geode.redis.internal.RedisConstants.ERROR_WRONG_SLOT; import static org.apache.geode.redis.internal.data.RedisSet.smove; import java.util.Arrays; @@ -23,7 +22,6 @@ import java.util.List; import org.apache.geode.redis.internal.commands.Command; import org.apache.geode.redis.internal.commands.executor.CommandExecutor; import org.apache.geode.redis.internal.commands.executor.RedisResponse; -import org.apache.geode.redis.internal.data.RedisDataMovedException; import org.apache.geode.redis.internal.data.RedisKey; import org.apache.geode.redis.internal.netty.ExecutionHandlerContext; import org.apache.geode.redis.internal.services.RegionProvider; @@ -38,13 +36,6 @@ public class SMoveExecutor implements CommandExecutor { byte[] member = commandElems.get(3); RegionProvider regionProvider = context.getRegionProvider(); - try { - regionProvider.ensureKeyIsLocal(sourceKey); - regionProvider.ensureKeyIsLocal(destKey); - } catch (RedisDataMovedException ex) { - return RedisResponse.crossSlot(ERROR_WRONG_SLOT); - } - int removed = context.lockedExecute(sourceKey, Arrays.asList(sourceKey, destKey), () -> smove(sourceKey, destKey, member, regionProvider)); diff --git a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/executor/set/SetOpExecutor.java b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/executor/set/SetOpExecutor.java index b83c8e2..9f6fce0 100755 --- a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/executor/set/SetOpExecutor.java +++ b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/executor/set/SetOpExecutor.java @@ -29,7 +29,6 @@ import org.apache.geode.redis.internal.commands.Command; import org.apache.geode.redis.internal.commands.RedisCommandType; import org.apache.geode.redis.internal.commands.executor.CommandExecutor; import org.apache.geode.redis.internal.commands.executor.RedisResponse; -import org.apache.geode.redis.internal.data.RedisDataMovedException; import org.apache.geode.redis.internal.data.RedisKey; import org.apache.geode.redis.internal.data.RedisSet; import org.apache.geode.redis.internal.data.RedisSet.MemberSet; @@ -49,13 +48,6 @@ public abstract class SetOpExecutor implements CommandExecutor { List<RedisKey> commandElements = command.getProcessedCommandKeys(); List<RedisKey> setKeys = commandElements.subList(setsStartIndex, commandElements.size()); RegionProvider regionProvider = context.getRegionProvider(); - try { - for (RedisKey k : setKeys) { - regionProvider.ensureKeyIsLocal(k); - } - } catch (RedisDataMovedException ex) { - return RedisResponse.error(ex.getMessage()); - } /* * SINTERSTORE currently use the else part of the code for their implementation. @@ -63,20 +55,24 @@ public abstract class SetOpExecutor implements CommandExecutor { * Refactor so the implementation is in the executor. After delete doActualSetOperation, * doStoreSetOp, doStoreSetOpWhileLocked, computeStoreSetOp, fetchSets */ + List<RedisKey> keysToLock = new ArrayList<>(setKeys); + if (isStorage()) { + keysToLock.add(command.getKey()); + } if (command.isOfType(RedisCommandType.SDIFF) || command.isOfType(RedisCommandType.SDIFFSTORE)) { if (isStorage()) { RedisKey destinationKey = command.getKey(); - int resultSize = context.lockedExecute(destinationKey, new ArrayList<>(setKeys), + int resultSize = context.lockedExecute(destinationKey, keysToLock, () -> sdiffstore(regionProvider, destinationKey, setKeys)); return RedisResponse.integer(resultSize); } - Set<byte[]> resultSet = context.lockedExecute(setKeys.get(0), new ArrayList<>(setKeys), + Set<byte[]> resultSet = context.lockedExecute(setKeys.get(0), keysToLock, () -> sdiff(regionProvider, setKeys)); return RedisResponse.array(resultSet, true); } else if (command.isOfType(RedisCommandType.SINTER)) { - Set<byte[]> resultSet = context.lockedExecute(setKeys.get(0), new ArrayList<>(setKeys), + Set<byte[]> resultSet = context.lockedExecute(setKeys.get(0), keysToLock, () -> sinter(regionProvider, setKeys)); return RedisResponse.array(resultSet, true); @@ -84,12 +80,12 @@ public abstract class SetOpExecutor implements CommandExecutor { || command.isOfType(RedisCommandType.SUNIONSTORE)) { if (isStorage()) { RedisKey destinationKey = command.getKey(); - int resultSize = context.lockedExecute(destinationKey, new ArrayList<>(setKeys), + int resultSize = context.lockedExecute(destinationKey, keysToLock, () -> sunionstore(regionProvider, destinationKey, setKeys)); return RedisResponse.integer(resultSize); } - Set<byte[]> resultSet = context.lockedExecute(setKeys.get(0), new ArrayList<>(setKeys), + Set<byte[]> resultSet = context.lockedExecute(setKeys.get(0), keysToLock, () -> sunion(regionProvider, setKeys)); return RedisResponse.array(resultSet, true); } diff --git a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/executor/sortedset/ZInterStoreExecutor.java b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/executor/sortedset/ZInterStoreExecutor.java index 0ddff26..64bbc6a 100644 --- a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/executor/sortedset/ZInterStoreExecutor.java +++ b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/executor/sortedset/ZInterStoreExecutor.java @@ -32,7 +32,7 @@ public class ZInterStoreExecutor extends ZStoreExecutor { List<ZKeyWeight> keyWeights, ZAggregator aggregator) { RegionProvider regionProvider = context.getRegionProvider(); RedisKey key = command.getKey(); - List<RedisKey> keysToLock = getKeysToLock(regionProvider, key, keyWeights); + List<RedisKey> keysToLock = getKeysToLock(key, keyWeights); return context.lockedExecute(key, keysToLock, () -> zinterstore(regionProvider, key, keyWeights, aggregator)); diff --git a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/executor/sortedset/ZStoreExecutor.java b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/executor/sortedset/ZStoreExecutor.java index c9aa5d7..335a35e 100644 --- a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/executor/sortedset/ZStoreExecutor.java +++ b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/executor/sortedset/ZStoreExecutor.java @@ -17,7 +17,6 @@ package org.apache.geode.redis.internal.commands.executor.sortedset; import static org.apache.geode.redis.internal.RedisConstants.ERROR_NOT_INTEGER; import static org.apache.geode.redis.internal.RedisConstants.ERROR_SYNTAX; import static org.apache.geode.redis.internal.RedisConstants.ERROR_WEIGHT_NOT_A_FLOAT; -import static org.apache.geode.redis.internal.RedisConstants.ERROR_WRONG_SLOT; import static org.apache.geode.redis.internal.netty.Coder.narrowLongToInt; import static org.apache.geode.redis.internal.netty.Coder.toUpperCaseBytes; import static org.apache.geode.redis.internal.netty.StringBytesGlossary.AGGREGATE; @@ -34,7 +33,6 @@ import org.apache.geode.redis.internal.commands.executor.RedisResponse; import org.apache.geode.redis.internal.data.RedisKey; import org.apache.geode.redis.internal.netty.Coder; import org.apache.geode.redis.internal.netty.ExecutionHandlerContext; -import org.apache.geode.redis.internal.services.RegionProvider; public abstract class ZStoreExecutor implements CommandExecutor { @@ -105,24 +103,14 @@ public abstract class ZStoreExecutor implements CommandExecutor { } } - int slot = command.getKey().getSlot(); - for (ZKeyWeight keyWeight : keyWeights) { - if (keyWeight.getKey().getSlot() != slot) { - return RedisResponse.crossSlot(ERROR_WRONG_SLOT); - } - } - return RedisResponse.integer(getResult(context, command, keyWeights, aggregator)); } - protected List<RedisKey> getKeysToLock(RegionProvider regionProvider, RedisKey destinationKey, - List<ZKeyWeight> keyWeights) { + protected List<RedisKey> getKeysToLock(RedisKey destinationKey, List<ZKeyWeight> keyWeights) { List<RedisKey> keysToLock = new ArrayList<>(keyWeights.size()); for (ZKeyWeight kw : keyWeights) { - regionProvider.ensureKeyIsLocal(kw.getKey()); keysToLock.add(kw.getKey()); } - regionProvider.ensureKeyIsLocal(destinationKey); keysToLock.add(destinationKey); return keysToLock; diff --git a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/executor/sortedset/ZUnionStoreExecutor.java b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/executor/sortedset/ZUnionStoreExecutor.java index 6d30155..dee9f1a 100644 --- a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/executor/sortedset/ZUnionStoreExecutor.java +++ b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/executor/sortedset/ZUnionStoreExecutor.java @@ -31,7 +31,7 @@ public class ZUnionStoreExecutor extends ZStoreExecutor { List<ZKeyWeight> keyWeights, ZAggregator aggregator) { RegionProvider regionProvider = context.getRegionProvider(); RedisKey key = command.getKey(); - List<RedisKey> keysToLock = getKeysToLock(regionProvider, key, keyWeights); + List<RedisKey> keysToLock = getKeysToLock(key, keyWeights); return context.lockedExecute(key, keysToLock, () -> zunionstore(regionProvider, key, keyWeights, aggregator)); diff --git a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/executor/string/AbstractMSetExecutor.java b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/executor/string/AbstractMSetExecutor.java index 4d7c4da..e23db4b 100644 --- a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/executor/string/AbstractMSetExecutor.java +++ b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/commands/executor/string/AbstractMSetExecutor.java @@ -66,15 +66,9 @@ public abstract class AbstractMSetExecutor implements CommandExecutor { protected void mset(ExecutionHandlerContext context, List<RedisKey> keys, List<byte[]> values, boolean nx) { - List<RedisKey> keysToLock = new ArrayList<>(keys.size()); + List<RedisKey> keysToLock = new ArrayList<>(keys); RegionProvider regionProvider = context.getRegionProvider(); - for (RedisKey key : keys) { - regionProvider.ensureKeyIsLocal(key); - keysToLock.add(key); - } - // Pass a key in so that the bucket will be locked. Since all keys are already guaranteed to be - // in the same bucket we can use any key for this. context.lockedExecuteInTransaction(keysToLock.get(0), keysToLock, () -> mset0(regionProvider, keys, values, nx)); } diff --git a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/data/RedisCrossSlotException.java b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/data/RedisCrossSlotException.java new file mode 100644 index 0000000..e05d46b --- /dev/null +++ b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/data/RedisCrossSlotException.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. You may obtain a + * copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.apache.geode.redis.internal.data; + +import org.apache.geode.redis.internal.RedisException; + +public class RedisCrossSlotException extends RedisException { + + private static final long serialVersionUID = -2126545465506588852L; + + public RedisCrossSlotException() { + super(); + } + + public RedisCrossSlotException(String message) { + super(message); + } +} diff --git a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/netty/ExecutionHandlerContext.java b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/netty/ExecutionHandlerContext.java index 1011eb7..000adf5 100644 --- a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/netty/ExecutionHandlerContext.java +++ b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/netty/ExecutionHandlerContext.java @@ -51,6 +51,7 @@ import org.apache.geode.redis.internal.commands.RedisCommandType; import org.apache.geode.redis.internal.commands.executor.RedisResponse; import org.apache.geode.redis.internal.commands.executor.UnknownExecutor; import org.apache.geode.redis.internal.commands.parameters.RedisParametersMismatchException; +import org.apache.geode.redis.internal.data.RedisCrossSlotException; import org.apache.geode.redis.internal.data.RedisData; import org.apache.geode.redis.internal.data.RedisDataMovedException; import org.apache.geode.redis.internal.data.RedisDataType; @@ -182,6 +183,8 @@ public class ExecutionHandlerContext extends ChannelInboundHandlerAdapter { return RedisResponse.moved(rootCause.getMessage()); } else if (rootCause instanceof RedisDataTypeMismatchException) { return RedisResponse.wrongType(rootCause.getMessage()); + } else if (rootCause instanceof RedisCrossSlotException) { + return RedisResponse.crossSlot(rootCause.getMessage()); } else if (rootCause instanceof IllegalStateException || rootCause instanceof RedisParametersMismatchException || rootCause instanceof RedisException diff --git a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/services/RegionProvider.java b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/services/RegionProvider.java index 0f8fdd2..55e37e8 100644 --- a/geode-for-redis/src/main/java/org/apache/geode/redis/internal/services/RegionProvider.java +++ b/geode-for-redis/src/main/java/org/apache/geode/redis/internal/services/RegionProvider.java @@ -14,6 +14,7 @@ */ package org.apache.geode.redis.internal.services; +import static org.apache.geode.redis.internal.RedisConstants.ERROR_WRONG_SLOT; import static org.apache.geode.redis.internal.RedisProperties.REDIS_REGION_NAME_PROPERTY; import static org.apache.geode.redis.internal.RedisProperties.REGION_BUCKETS; import static org.apache.geode.redis.internal.RedisProperties.getIntegerSystemProperty; @@ -27,6 +28,7 @@ import java.util.List; import java.util.Set; import java.util.concurrent.Callable; +import org.apache.geode.annotations.VisibleForTesting; import org.apache.geode.cache.CacheTransactionManager; import org.apache.geode.cache.PartitionAttributesFactory; import org.apache.geode.cache.Region; @@ -46,6 +48,7 @@ import org.apache.geode.internal.cache.execute.BucketMovedException; import org.apache.geode.redis.internal.RedisConstants; import org.apache.geode.redis.internal.RedisException; import org.apache.geode.redis.internal.commands.executor.cluster.RedisPartitionResolver; +import org.apache.geode.redis.internal.data.RedisCrossSlotException; import org.apache.geode.redis.internal.data.RedisData; import org.apache.geode.redis.internal.data.RedisDataMovedException; import org.apache.geode.redis.internal.data.RedisDataType; @@ -63,13 +66,12 @@ public class RegionProvider { * The name of the region that holds data stored in redis. */ public static final String DEFAULT_REDIS_REGION_NAME = "GEODE_FOR_REDIS"; - public static final String REDIS_REGION_BUCKETS_PARAM = REGION_BUCKETS; public static final int REDIS_SLOTS = 16384; // Ideally the bucket count should be a power of 2, but technically it is not required. public static final int REDIS_REGION_BUCKETS = - getIntegerSystemProperty(REDIS_REGION_BUCKETS_PARAM, 128, 1, REDIS_SLOTS); + getIntegerSystemProperty(REGION_BUCKETS, 128, 1, REDIS_SLOTS); public static final int REDIS_SLOTS_PER_BUCKET = REDIS_SLOTS / REDIS_REGION_BUCKETS; @@ -144,6 +146,9 @@ public class RegionProvider { public <T> T lockedExecute(RedisKey key, List<RedisKey> keysToLock, Callable<T> callable) { try { + if (areKeysCrossSlots(keysToLock)) { + throw new RedisCrossSlotException(ERROR_WRONG_SLOT); + } return partitionedRegion.computeWithPrimaryLocked(key, () -> stripedCoordinator.execute(keysToLock, callable)); } catch (PrimaryBucketLockException | BucketMovedException | RegionDestroyedException ex) { @@ -155,6 +160,17 @@ public class RegionProvider { } } + @VisibleForTesting + static boolean areKeysCrossSlots(List<RedisKey> keysToLock) { + int slot = keysToLock.get(0).getSlot(); + for (RedisKey key : keysToLock) { + if (key.getSlot() != slot) { + return true; + } + } + return false; + } + /** * Execute the given Callable in the context of a GemFire transaction. On failure there is no * attempt to retry. diff --git a/geode-for-redis/src/test/java/org/apache/geode/redis/internal/services/RegionProviderTest.java b/geode-for-redis/src/test/java/org/apache/geode/redis/internal/services/RegionProviderTest.java new file mode 100644 index 0000000..ea1433c --- /dev/null +++ b/geode-for-redis/src/test/java/org/apache/geode/redis/internal/services/RegionProviderTest.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. You may obtain a + * copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.apache.geode.redis.internal.services; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; + +import org.apache.geode.redis.internal.data.RedisKey; + +public class RegionProviderTest { + + @Test + public void areKeysCrossSlotsReturnsFalseWhenKeysAreSameSlot() { + RedisKey key1 = mock(RedisKey.class); + int slot1 = 1; + when(key1.getSlot()).thenReturn(slot1); + RedisKey key2 = mock(RedisKey.class); + when(key2.getSlot()).thenReturn(slot1); + + List<RedisKey> keyList = Arrays.asList(key1, key2); + + assertThat(RegionProvider.areKeysCrossSlots(keyList)).isFalse(); + } + + @Test + public void areKeysCrossSlotsReturnsTrueWhenKeysAreCrossSlots() { + RedisKey key1 = mock(RedisKey.class); + int slot1 = 1; + when(key1.getSlot()).thenReturn(slot1); + RedisKey key2 = mock(RedisKey.class); + int slot2 = 2; + when(key2.getSlot()).thenReturn(slot2); + + List<RedisKey> keyList = Arrays.asList(key1, key2); + + assertThat(RegionProvider.areKeysCrossSlots(keyList)).isTrue(); + } + + @Test + public void areKeysCrossSlotsReturnsTrueWhenKeysAreCrossSlotsForManyKeys() { + List<RedisKey> keyList = new ArrayList<>(); + for (int i = 0; i < 100; ++i) { + RedisKey key = mock(RedisKey.class); + int slot1 = 1; + when(key.getSlot()).thenReturn(slot1); + keyList.add(key); + } + RedisKey finalKey = mock(RedisKey.class); + int slot2 = 2; + when(finalKey.getSlot()).thenReturn(slot2); + keyList.add(finalKey); + + assertThat(RegionProvider.areKeysCrossSlots(keyList)).isTrue(); + } +}