diff --git a/pom.xml b/pom.xml index 0d8ecda453..9f9176d895 100644 --- a/pom.xml +++ b/pom.xml @@ -348,6 +348,7 @@ src/test/java/redis/clients/jedis/commands/jedis/ClusterStreamsCommandsTest.java src/test/java/redis/clients/jedis/commands/jedis/PooledStreamsCommandsTest.java src/test/java/redis/clients/jedis/resps/StreamEntryDeletionResultTest.java + **/*FunctionCommandsTest* diff --git a/src/main/java/redis/clients/jedis/JedisClusterInfoCache.java b/src/main/java/redis/clients/jedis/JedisClusterInfoCache.java index 42574bdbba..ab8b6622c3 100644 --- a/src/main/java/redis/clients/jedis/JedisClusterInfoCache.java +++ b/src/main/java/redis/clients/jedis/JedisClusterInfoCache.java @@ -39,6 +39,7 @@ public class JedisClusterInfoCache { private static final Logger logger = LoggerFactory.getLogger(JedisClusterInfoCache.class); private final Map nodes = new HashMap<>(); + private final Map primaryNodesCache = new HashMap<>(); private final ConnectionPool[] slots = new ConnectionPool[Protocol.CLUSTER_HASHSLOTS]; private final HostAndPort[] slotNodes = new HostAndPort[Protocol.CLUSTER_HASHSLOTS]; private final List[] replicaSlots; @@ -176,6 +177,7 @@ public void discoverClusterNodesAndSlots(Connection jedis) { HostAndPort targetNode = generateHostAndPort(hostInfos); setupNodeIfNotExist(targetNode); if (i == MASTER_NODE_INDEX) { + primaryNodesCache.put(getNodeKey(targetNode), getNode(targetNode)); assignSlotsToNode(slotNums, targetNode); } else if (clientConfig.isReadOnlyForRedisClusterReplicas()) { assignSlotsToReplicaNode(slotNums, targetNode); @@ -425,6 +427,26 @@ public Map getNodes() { } } + public Map getPrimaryNodes() { + r.lock(); + try { + return new HashMap<>(primaryNodesCache); + } finally { + r.unlock(); + } + } + + public List getShuffledPrimaryNodesPool() { + r.lock(); + try { + List pools = new ArrayList<>(primaryNodesCache.values()); + Collections.shuffle(pools); + return pools; + } finally { + r.unlock(); + } + } + public List getShuffledNodesPool() { r.lock(); try { @@ -475,6 +497,7 @@ private void resetNodes() { } } nodes.clear(); + primaryNodesCache.clear(); } public void close() { diff --git a/src/main/java/redis/clients/jedis/executors/ClusterCommandExecutor.java b/src/main/java/redis/clients/jedis/executors/ClusterCommandExecutor.java index 766e2660c9..e475a40948 100644 --- a/src/main/java/redis/clients/jedis/executors/ClusterCommandExecutor.java +++ b/src/main/java/redis/clients/jedis/executors/ClusterCommandExecutor.java @@ -45,7 +45,7 @@ public void close() { @Override public final T broadcastCommand(CommandObject commandObject) { - Map connectionMap = provider.getConnectionMap(); + Map connectionMap = provider.getPrimaryNodesConnectionMap(); boolean isErrored = false; T reply = null; diff --git a/src/main/java/redis/clients/jedis/providers/ClusterConnectionProvider.java b/src/main/java/redis/clients/jedis/providers/ClusterConnectionProvider.java index d7e2f9fee7..2f2e62dac8 100644 --- a/src/main/java/redis/clients/jedis/providers/ClusterConnectionProvider.java +++ b/src/main/java/redis/clients/jedis/providers/ClusterConnectionProvider.java @@ -112,6 +112,10 @@ public Map getNodes() { return cache.getNodes(); } + public Map getPrimaryNodes() { + return cache.getPrimaryNodes(); + } + public HostAndPort getNode(int slot) { return slot >= 0 ? cache.getSlotNode(slot) : null; } @@ -136,7 +140,7 @@ public Connection getConnection() { // In antirez's redis-rb-cluster implementation, getRandomConnection always return // valid connection (able to ping-pong) or exception if all connections are invalid - List pools = cache.getShuffledNodesPool(); + List pools = cache.getShuffledPrimaryNodesPool(); JedisException suppressed = null; for (ConnectionPool pool : pools) { @@ -205,8 +209,15 @@ public Connection getReplicaConnectionFromSlot(int slot) { return getConnectionFromSlot(slot); } + @Override public Map getConnectionMap() { return Collections.unmodifiableMap(getNodes()); } + + @Override + public Map getPrimaryNodesConnectionMap() { + return Collections.unmodifiableMap(getPrimaryNodes()); + } + } diff --git a/src/main/java/redis/clients/jedis/providers/ConnectionProvider.java b/src/main/java/redis/clients/jedis/providers/ConnectionProvider.java index 48543dd5cb..37c6c9c685 100644 --- a/src/main/java/redis/clients/jedis/providers/ConnectionProvider.java +++ b/src/main/java/redis/clients/jedis/providers/ConnectionProvider.java @@ -15,4 +15,9 @@ public interface ConnectionProvider extends AutoCloseable { final Connection c = getConnection(); return Collections.singletonMap(c.toString(), c); } + + default Map getPrimaryNodesConnectionMap() { + final Connection c = getConnection(); + return Collections.singletonMap(c.toString(), c); + } } diff --git a/src/test/java/redis/clients/jedis/ClusterCommandExecutorTest.java b/src/test/java/redis/clients/jedis/ClusterCommandExecutorTest.java index d6437e84ea..0ff04a832a 100644 --- a/src/test/java/redis/clients/jedis/ClusterCommandExecutorTest.java +++ b/src/test/java/redis/clients/jedis/ClusterCommandExecutorTest.java @@ -19,7 +19,6 @@ import org.mockito.InOrder; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; import redis.clients.jedis.exceptions.JedisAskDataException; import redis.clients.jedis.exceptions.JedisClusterOperationException; diff --git a/src/test/java/redis/clients/jedis/JedisClusterInfoCacheTest.java b/src/test/java/redis/clients/jedis/JedisClusterInfoCacheTest.java index fa7b288360..691d8ed01c 100644 --- a/src/test/java/redis/clients/jedis/JedisClusterInfoCacheTest.java +++ b/src/test/java/redis/clients/jedis/JedisClusterInfoCacheTest.java @@ -15,12 +15,16 @@ import java.util.stream.Collectors; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.aMapWithSize; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.hasItem; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.when; +import static redis.clients.jedis.JedisClusterInfoCache.getNodeKey; import static redis.clients.jedis.Protocol.Command.CLUSTER; import static redis.clients.jedis.util.CommandArgumentMatchers.commandWithArgs; @@ -49,7 +53,7 @@ public void testReplicaNodeRemovalAndRediscovery() { // Mock the cluster slots responses when(mockConnection.executeCommand(argThat(commandWithArgs(CLUSTER, "SLOTS")))).thenReturn( - masterReplicaSlotsResponse()).thenReturn(masterOnlySlotsResponse()) + masterReplicaSlotsResponse(MASTER_HOST, REPLICA_1_HOST)).thenReturn(masterOnlySlotsResponse()) .thenReturn(masterReplica2SlotsResponse()); // Initial discovery with one master and one replica (replica-1) @@ -78,7 +82,7 @@ public void testResetWithReplicaSlots() { // Mock the cluster slots responses when(mockConnection.executeCommand(argThat(commandWithArgs(CLUSTER, "SLOTS")))).thenReturn( - masterReplicaSlotsResponse()); + masterReplicaSlotsResponse(MASTER_HOST, REPLICA_1_HOST)); // Initial discovery cache.discoverClusterNodesAndSlots(mockConnection); @@ -94,10 +98,68 @@ public void testResetWithReplicaSlots() { assertReplicasAvailable(cache, REPLICA_1_HOST); } - private List masterReplicaSlotsResponse() { + @Test + public void getPrimaryNodesAfterReplicaNodeRemovalAndRediscovery() { + // Create client config with read-only replicas enabled + JedisClientConfig clientConfig = DefaultJedisClientConfig.builder() + .readOnlyForRedisClusterReplicas().build(); + + Set startNodes = new HashSet<>(); + startNodes.add(MASTER_HOST); + + JedisClusterInfoCache cache = new JedisClusterInfoCache(clientConfig, startNodes); + + // Mock the cluster slots responses + when(mockConnection.executeCommand(argThat(commandWithArgs(CLUSTER, "SLOTS")))).thenReturn( + masterReplicaSlotsResponse(MASTER_HOST, REPLICA_1_HOST)).thenReturn(masterOnlySlotsResponse()) + .thenReturn(masterReplica2SlotsResponse()); + + // Initial discovery with one master and one replica (replica-1) + cache.discoverClusterNodesAndSlots(mockConnection); + assertThat(cache.getPrimaryNodes(),aMapWithSize(1)); + assertThat(cache.getPrimaryNodes(), + hasEntry(equalTo(getNodeKey(MASTER_HOST)), equalTo(cache.getNode(MASTER_HOST)))); + + // Simulate rediscovery - master only + cache.discoverClusterNodesAndSlots(mockConnection); + assertThat( cache.getPrimaryNodes(),aMapWithSize(1)); + assertThat(cache.getPrimaryNodes(), + hasEntry(equalTo(getNodeKey(MASTER_HOST)), equalTo(cache.getNode(MASTER_HOST)))); + } + + @Test + public void getPrimaryNodesAfterMasterReplicaFailover() { + // Create client config with read-only replicas enabled + JedisClientConfig clientConfig = DefaultJedisClientConfig.builder() + .readOnlyForRedisClusterReplicas().build(); + + Set startNodes = new HashSet<>(); + startNodes.add(MASTER_HOST); + + JedisClusterInfoCache cache = new JedisClusterInfoCache(clientConfig, startNodes); + + // Mock the cluster slots responses + when(mockConnection.executeCommand(argThat(commandWithArgs(CLUSTER, "SLOTS")))) + .thenReturn(masterReplicaSlotsResponse(MASTER_HOST, REPLICA_1_HOST)) + .thenReturn(masterReplicaSlotsResponse(REPLICA_1_HOST, MASTER_HOST)); + + // Initial discovery with one master and one replica (replica-1) + cache.discoverClusterNodesAndSlots(mockConnection); + assertThat(cache.getPrimaryNodes(),aMapWithSize(1)); + assertThat(cache.getPrimaryNodes(), + hasEntry(equalTo(getNodeKey(MASTER_HOST)), equalTo(cache.getNode(MASTER_HOST)))); + + // Simulate rediscovery - master only + cache.discoverClusterNodesAndSlots(mockConnection); + assertThat( cache.getPrimaryNodes(),aMapWithSize(1)); + assertThat(cache.getPrimaryNodes(), + hasEntry(equalTo(getNodeKey(REPLICA_1_HOST)), equalTo(cache.getNode(REPLICA_1_HOST)))); + } + + private List masterReplicaSlotsResponse(HostAndPort masterHost, HostAndPort replicaHost) { return createClusterSlotsResponse( - new SlotRange.Builder(0, 16383).master(MASTER_HOST, "master-id-1") - .replica(REPLICA_1_HOST, "replica-id-1").build()); + new SlotRange.Builder(0, 16383).master(masterHost, masterHost.toString() + "-id") + .replica(replicaHost, replicaHost.toString() + "-id").build()); } private List masterOnlySlotsResponse() { diff --git a/src/test/java/redis/clients/jedis/commands/unified/FunctionCommandsTestBase.java b/src/test/java/redis/clients/jedis/commands/unified/FunctionCommandsTestBase.java new file mode 100644 index 0000000000..9b293ac93b --- /dev/null +++ b/src/test/java/redis/clients/jedis/commands/unified/FunctionCommandsTestBase.java @@ -0,0 +1,271 @@ +package redis.clients.jedis.commands.unified; + +import io.redis.test.annotations.SinceRedisVersion; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import redis.clients.jedis.RedisProtocol; +import redis.clients.jedis.args.FlushMode; +import redis.clients.jedis.args.FunctionRestorePolicy; +import redis.clients.jedis.exceptions.JedisException; +import redis.clients.jedis.resps.FunctionStats; +import redis.clients.jedis.resps.LibraryInfo; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public abstract class FunctionCommandsTestBase extends UnifiedJedisCommandsTestBase { + final String libraryName = "mylib"; + final String TEST_LUA_SCRIPT_TMPL = "#!lua name=%s\n" + + "redis.register_function('%s', function(keys, args) return %s end)"; + + private String functionName; + + public FunctionCommandsTestBase(RedisProtocol protocol) { + super(protocol); + } + + protected void setUpFunctions(TestInfo info) { + functionName = info.getDisplayName().replaceAll("[^a-zA-Z0-9]", "_"); + jedis.functionLoad(String.format(TEST_LUA_SCRIPT_TMPL, libraryName, functionName, "42")); + } + + protected void cleanUpFunctions() { + try { + jedis.functionDelete(libraryName); + } catch (JedisException e) { + // ignore + } + } + + @Test + @SinceRedisVersion(value = "7.0.0") + public void testFunctionDeletion() { + List listResponse = jedis.functionList(); + + assertThat(listResponse, hasSize(1)); + assertThat(listResponse.get(0).getLibraryName(), equalTo(libraryName)); + assertThat(listResponse.get(0).getFunctions(), hasSize(1)); + assertThat(listResponse.get(0).getFunctions().get(0), hasEntry("name", functionName)); + + String delete = jedis.functionDelete(libraryName); + assertThat(delete, equalTo("OK")); + + listResponse = jedis.functionList(); + assertThat(listResponse, empty()); + } + + @Test + @SinceRedisVersion(value = "7.0.0") + public void testFunctionDeletionBinary() { + List listResponse = jedis.functionList(); + + assertThat(listResponse, hasSize(1)); + assertThat(listResponse.get(0).getLibraryName(), equalTo(libraryName)); + assertThat(listResponse.get(0).getFunctions(), hasSize(1)); + assertThat(listResponse.get(0).getFunctions().get(0), hasEntry("name", functionName)); + + String deleteBinary = jedis.functionDelete(libraryName.getBytes()); + assertThat(deleteBinary, equalTo("OK")); + + listResponse = jedis.functionList(); + assertThat(listResponse, empty()); + } + + @Test + @SinceRedisVersion(value = "7.0.0") + public void testFunctionListing() { + + List list = jedis.functionList(); + + assertThat(list, hasSize(1)); + assertThat(list.get(0).getLibraryName(), equalTo(libraryName)); + assertThat(list.get(0).getFunctions(), hasSize(1)); + assertThat(list.get(0).getFunctions().get(0), hasEntry("name", functionName)); + assertThat(list.get(0).getLibraryCode(), nullValue()); + + List listBinary = jedis.functionListBinary(); + + assertThat(listBinary, hasSize(1)); + + List listLibrary = jedis.functionList(libraryName); + + assertThat(listLibrary, hasSize(1)); + assertThat(listLibrary.get(0).getLibraryName(), equalTo(libraryName)); + assertThat(listLibrary.get(0).getFunctions(), hasSize(1)); + assertThat(listLibrary.get(0).getFunctions().get(0), hasEntry("name", functionName)); + assertThat(listLibrary.get(0).getLibraryCode(), nullValue()); + + List listLibraryBinary = jedis.functionList(libraryName.getBytes()); + + assertThat(listLibraryBinary, hasSize(1)); + + List listWithCode = jedis.functionListWithCode(); + + assertThat(listWithCode, hasSize(1)); + assertThat(listWithCode.get(0).getLibraryName(), equalTo(libraryName)); + assertThat(listWithCode.get(0).getFunctions(), hasSize(1)); + assertThat(listWithCode.get(0).getFunctions().get(0), hasEntry("name", functionName)); + assertThat(listWithCode.get(0).getLibraryCode(), notNullValue()); + + List listWithCodeBinary = jedis.functionListWithCodeBinary(); + + assertThat(listWithCodeBinary, hasSize(1)); + + List listWithCodeLibrary = jedis.functionListWithCode(libraryName); + + assertThat(listWithCodeLibrary, hasSize(1)); + assertThat(listWithCodeLibrary.get(0).getLibraryName(), equalTo(libraryName)); + assertThat(listWithCodeLibrary.get(0).getFunctions(), hasSize(1)); + assertThat(listWithCodeLibrary.get(0).getFunctions().get(0), hasEntry("name", functionName)); + assertThat(listWithCodeLibrary.get(0).getLibraryCode(), notNullValue()); + + List listWithCodeLibraryBinary = jedis.functionListWithCode(libraryName.getBytes()); + + assertThat(listWithCodeLibraryBinary, hasSize(1)); + } + + @Test + @SinceRedisVersion(value = "7.0.0") + public void testFunctionReload() { + Object result = jedis.fcall(functionName.getBytes(), new ArrayList<>(), new ArrayList<>()); + assertThat(result, equalTo(42L)); + + String luaScriptChanged = String.format(TEST_LUA_SCRIPT_TMPL, libraryName, functionName, "52"); + String replaceResult = jedis.functionLoadReplace(luaScriptChanged); + assertThat(replaceResult, equalTo("mylib")); + + Object resultAfter = jedis.fcall(functionName.getBytes(), new ArrayList<>(), new ArrayList<>()); + assertThat(resultAfter, equalTo(52L)); + } + + @Test + @SinceRedisVersion(value = "7.0.0") + public void testFunctionReloadBinary() { + Object result = jedis.fcall(functionName.getBytes(), new ArrayList<>(), new ArrayList<>()); + assertThat(result, equalTo(42L)); + + String luaScriptChanged = String.format(TEST_LUA_SCRIPT_TMPL, libraryName, functionName, "52"); + String replaceResult = jedis.functionLoadReplace(luaScriptChanged.getBytes()); + assertThat(replaceResult, equalTo("mylib")); + + Object resultAfter = jedis.fcall(functionName.getBytes(), new ArrayList<>(), new ArrayList<>()); + assertThat(resultAfter, equalTo(52L)); + } + + @Test + @SinceRedisVersion(value = "7.0.0") + public void testFunctionStats() { + + for (int i = 0; i < 5; i++) { + Object result = jedis.fcall(functionName.getBytes(), new ArrayList<>(), new ArrayList<>()); + assertThat(result, equalTo(42L)); + } + + FunctionStats stats = jedis.functionStats(); + + assertThat(stats, notNullValue()); + assertThat(stats.getEngines(), hasKey("LUA")); + Map luaStats = stats.getEngines().get("LUA"); + assertThat(luaStats, hasEntry("libraries_count", 1L)); + assertThat(luaStats, hasEntry("functions_count", 1L)); + + Object statsBinary = jedis.functionStatsBinary(); + + assertThat(statsBinary, notNullValue()); + } + + @Test + @SinceRedisVersion(value = "7.0.0") + public void testFunctionDumpFlushRestore() { + + List list = jedis.functionList(); + assertThat(list, hasSize(1)); + assertThat(list.get(0).getLibraryName(), equalTo(libraryName)); + assertThat(list.get(0).getFunctions(), hasSize(1)); + assertThat(list.get(0).getFunctions().get(0), hasEntry("name", functionName)); + + byte[] dump = jedis.functionDump(); + assertThat(dump, notNullValue()); + + String flush = jedis.functionFlush(); + assertThat(flush, equalTo("OK")); + + list = jedis.functionList(); + assertThat(list, empty()); + + String restore = jedis.functionRestore(dump); + assertThat(restore, equalTo("OK")); + + list = jedis.functionList(); + assertThat(list, hasSize(1)); + assertThat(list.get(0).getLibraryName(), equalTo(libraryName)); + assertThat(list.get(0).getFunctions(), hasSize(1)); + assertThat(list.get(0).getFunctions().get(0), hasEntry("name", functionName)); + } + + @Test + @SinceRedisVersion(value = "7.0.0") + public void testFunctionDumpFlushRestoreWithPolicy() { + + List list = jedis.functionList(); + assertThat(list, hasSize(1)); + assertThat(list.get(0).getLibraryName(), equalTo(libraryName)); + assertThat(list.get(0).getFunctions(), hasSize(1)); + assertThat(list.get(0).getFunctions().get(0), hasEntry("name", functionName)); + + byte[] dump = jedis.functionDump(); + assertThat(dump, notNullValue()); + + String flush = jedis.functionFlush(); + assertThat(flush, equalTo("OK")); + + list = jedis.functionList(); + assertThat(list, empty()); + + String restore = jedis.functionRestore(dump, FunctionRestorePolicy.REPLACE); + assertThat(restore, equalTo("OK")); + + list = jedis.functionList(); + assertThat(list, hasSize(1)); + assertThat(list.get(0).getLibraryName(), equalTo(libraryName)); + assertThat(list.get(0).getFunctions(), hasSize(1)); + assertThat(list.get(0).getFunctions().get(0), hasEntry("name", functionName)); + } + + @Test + @SinceRedisVersion(value = "7.0.0") + public void testFunctionFlushWithMode() { + + List list = jedis.functionList(); + + assertThat(list, hasSize(1)); + assertThat(list.get(0).getLibraryName(), equalTo(libraryName)); + assertThat(list.get(0).getFunctions(), hasSize(1)); + assertThat(list.get(0).getFunctions().get(0), hasEntry("name", functionName)); + + String flush = jedis.functionFlush(FlushMode.SYNC); + assertThat(flush, equalTo("OK")); + + list = jedis.functionList(); + assertThat(list, empty()); + } + + @Test + @SinceRedisVersion(value = "7.0.0") + public void testFunctionKill() { + JedisException e = assertThrows(JedisException.class, () -> jedis.functionKill()); + assertThat(e.getMessage(), containsString("No scripts in execution right now")); + } +} diff --git a/src/test/java/redis/clients/jedis/commands/unified/cluster/ClusterFunctionCommandsTest.java b/src/test/java/redis/clients/jedis/commands/unified/cluster/ClusterFunctionCommandsTest.java new file mode 100644 index 0000000000..c7848076fd --- /dev/null +++ b/src/test/java/redis/clients/jedis/commands/unified/cluster/ClusterFunctionCommandsTest.java @@ -0,0 +1,73 @@ +package redis.clients.jedis.commands.unified.cluster; + +import io.redis.test.annotations.SinceRedisVersion; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; +import redis.clients.jedis.DefaultJedisClientConfig; +import redis.clients.jedis.HostAndPorts; +import redis.clients.jedis.RedisProtocol; +import redis.clients.jedis.commands.unified.FunctionCommandsTestBase; +import redis.clients.jedis.exceptions.JedisBroadcastException; +import redis.clients.jedis.exceptions.JedisException; +import redis.clients.jedis.util.EnabledOnCommandCondition; +import redis.clients.jedis.util.RedisVersionCondition; + +import java.util.List; +import java.util.stream.Collectors; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.everyItem; +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@ParameterizedClass +@MethodSource("redis.clients.jedis.commands.CommandsTestsParameters#respVersions") +public class ClusterFunctionCommandsTest extends FunctionCommandsTestBase { + + public ClusterFunctionCommandsTest(RedisProtocol protocol) { + super(protocol); + } + + @RegisterExtension + public RedisVersionCondition versionCondition = new RedisVersionCondition( + HostAndPorts.getStableClusterServers().get(0), + DefaultJedisClientConfig.builder().password("cluster").build()); + @RegisterExtension + public EnabledOnCommandCondition enabledOnCommandCondition = new EnabledOnCommandCondition( + HostAndPorts.getStableClusterServers().get(0), + DefaultJedisClientConfig.builder().password("cluster").build()); + + @BeforeEach + public void setUp(TestInfo testInfo) { + jedis = ClusterCommandsTestHelper.getCleanCluster(protocol); + super.setUpFunctions(testInfo); + } + + @AfterEach + public void tearDown() { + super.cleanUpFunctions(); + jedis.close(); + ClusterCommandsTestHelper.clearClusterData(); + } + + @Test + @SinceRedisVersion(value = "7.0.0") + @Override + public void testFunctionKill() { + JedisException e = assertThrows(JedisException.class, () -> jedis.functionKill()); + assertThat(e, instanceOf(JedisBroadcastException.class)); + JedisBroadcastException jbe = (JedisBroadcastException) e; + List replies = jbe.getReplies().values().stream().map(e1 -> (Exception) e1) + .map(Exception::getMessage).collect(Collectors.toList()); + assertThat(replies.size(), equalTo(3)); + assertThat(replies, everyItem(containsString("No scripts in execution right now"))); + } + +} diff --git a/src/test/resources/env/docker-compose.yml b/src/test/resources/env/docker-compose.yml index bcdeef5835..86239cf9c6 100644 --- a/src/test/resources/env/docker-compose.yml +++ b/src/test/resources/env/docker-compose.yml @@ -131,15 +131,15 @@ services: image: "${CLIENT_LIBS_TEST_IMAGE}:${REDIS_VERSION}" container_name: cluster-stable-1 #network_mode: host - command: --cluster-announce-ip 127.0.0.1 + command: --cluster-announce-ip 127.0.0.1 --cluster-node-timeout 150 environment: - REDIS_CLUSTER=yes - REDIS_PASSWORD=cluster - PORT=7479 - - NODES=3 - - REPLICAS=0 + - NODES=6 + - REPLICAS=1 ports: - - "7479-7481:7479-7481" + - "7479-7484:7479-7484" volumes: - ${REDIS_ENV_CONF_DIR}/cluster-stable/config:/redis/config:r - ${REDIS_ENV_WORK_DIR}/cluster-stable/work:/redis/work:rw