package com.lambdaworks.redis.cluster; import static com.lambdaworks.redis.ScriptOutputType.STATUS; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Fail.fail; import java.util.*; import java.util.concurrent.TimeUnit; import com.lambdaworks.redis.internal.LettuceSets; import org.junit.After; import org.junit.Before; import org.junit.Test; import com.lambdaworks.Wait; import com.lambdaworks.redis.RedisCommandExecutionException; import com.lambdaworks.redis.RedisCommandTimeoutException; import com.lambdaworks.redis.api.sync.RedisCommands; import com.lambdaworks.redis.cluster.api.StatefulRedisClusterConnection; import com.lambdaworks.redis.cluster.api.sync.Executions; import com.lambdaworks.redis.cluster.api.sync.NodeSelection; import com.lambdaworks.redis.cluster.api.sync.RedisAdvancedClusterCommands; import com.lambdaworks.redis.cluster.models.partitions.Partitions; import com.lambdaworks.redis.cluster.models.partitions.RedisClusterNode; /** * @author Mark Paluch */ public class NodeSelectionSyncTest extends AbstractClusterTest { private RedisAdvancedClusterCommands<String, String> commands; private StatefulRedisClusterConnection<String, String> clusterConnection; @Before public void before() throws Exception { clusterClient.reloadPartitions(); clusterConnection = clusterClient.connect(); commands = clusterConnection.sync(); } @After public void after() throws Exception { commands.close(); } @Test public void testMultiNodeOperations() throws Exception { List<String> expectation = new ArrayList<>(); for (char c = 'a'; c < 'z'; c++) { String key = new String(new char[] { c, c, c }); expectation.add(key); commands.set(key, value); } List<String> result = new Vector<>(); Executions<Long> executions = commands.masters().commands().keys(result::add, "*"); assertThat(executions).hasSize(2); Collections.sort(expectation); Collections.sort(result); assertThat(result).isEqualTo(expectation); } @Test public void testNodeSelectionCount() throws Exception { assertThat(commands.all().size()).isEqualTo(4); assertThat(commands.slaves().size()).isEqualTo(2); assertThat(commands.masters().size()).isEqualTo(2); assertThat(commands.nodes(redisClusterNode -> redisClusterNode.is(RedisClusterNode.NodeFlag.MYSELF)).size()).isEqualTo( 1); } @Test public void testNodeSelection() throws Exception { NodeSelection<String, String> onlyMe = commands.nodes(redisClusterNode -> redisClusterNode.getFlags().contains( RedisClusterNode.NodeFlag.MYSELF)); Map<RedisClusterNode, RedisCommands<String, String>> map = onlyMe.asMap(); assertThat(map).hasSize(1); RedisCommands<String, String> node = onlyMe.commands(0); assertThat(node).isNotNull(); RedisClusterNode redisClusterNode = onlyMe.node(0); assertThat(redisClusterNode.getFlags()).contains(RedisClusterNode.NodeFlag.MYSELF); assertThat(onlyMe.asMap()).hasSize(1); } @Test public void testDynamicNodeSelection() throws Exception { Partitions partitions = commands.getStatefulConnection().getPartitions(); partitions.forEach(redisClusterNode -> redisClusterNode.setFlags(Collections.singleton(RedisClusterNode.NodeFlag.MASTER))); NodeSelection<String, String> selection = commands.nodes( redisClusterNode -> redisClusterNode.getFlags().contains(RedisClusterNode.NodeFlag.MYSELF), true); assertThat(selection.asMap()).hasSize(0); partitions.getPartition(0) .setFlags(LettuceSets.unmodifiableSet(RedisClusterNode.NodeFlag.MYSELF, RedisClusterNode.NodeFlag.MASTER)); assertThat(selection.asMap()).hasSize(1); partitions.getPartition(1) .setFlags(LettuceSets.unmodifiableSet(RedisClusterNode.NodeFlag.MYSELF, RedisClusterNode.NodeFlag.MASTER)); assertThat(selection.asMap()).hasSize(2); } @Test public void testNodeSelectionPing() throws Exception { NodeSelection<String, String> onlyMe = commands.nodes(redisClusterNode -> redisClusterNode.getFlags().contains( RedisClusterNode.NodeFlag.MYSELF)); Map<RedisClusterNode, RedisCommands<String, String>> map = onlyMe.asMap(); assertThat(map).hasSize(1); Executions<String> ping = onlyMe.commands().ping(); assertThat(ping.get(onlyMe.node(0))).isEqualTo("PONG"); } @Test public void testStaticNodeSelection() throws Exception { NodeSelection<String, String> selection = commands.nodes( redisClusterNode -> redisClusterNode.getFlags().contains(RedisClusterNode.NodeFlag.MYSELF), false); assertThat(selection.asMap()).hasSize(1); commands.getStatefulConnection().getPartitions().getPartition(2) .setFlags(Collections.singleton(RedisClusterNode.NodeFlag.MYSELF)); assertThat(selection.asMap()).hasSize(1); } @Test public void testAsynchronicityOfMultiNodeExecution() throws Exception { RedisAdvancedClusterCommands<String, String> connection2 = clusterClient.connect().sync(); connection2.setTimeout(1, TimeUnit.SECONDS); NodeSelection<String, String> masters = connection2.masters(); masters.commands().configSet("lua-time-limit", "10"); Executions<Object> eval = null; try { eval = masters.commands().eval("while true do end", STATUS, new String[0]); fail("missing exception"); } catch (RedisCommandTimeoutException e) { assertThat(e).hasMessageContaining("Command timed out for node(s)"); } Executions<String> kill = commands.masters().commands().scriptKill(); } @Test public void testSlavesReadWrite() throws Exception { NodeSelection<String, String> nodes = commands.nodes(redisClusterNode -> redisClusterNode.getFlags().contains( RedisClusterNode.NodeFlag.SLAVE)); assertThat(nodes.size()).isEqualTo(2); commands.set(key, value); waitForReplication(key, port4); try { nodes.commands().get(key); fail("Missing RedisCommandExecutionException: MOVED"); } catch (RedisCommandExecutionException e) { assertThat(e.getSuppressed().length).isGreaterThan(0); } } @Test public void testSlavesWithReadOnly() throws Exception { int slot = SlotHash.getSlot(key); Optional<RedisClusterNode> master = clusterConnection.getPartitions().getPartitions().stream() .filter(redisClusterNode -> redisClusterNode.hasSlot(slot)).findFirst(); NodeSelection<String, String> nodes = commands.slaves(redisClusterNode -> redisClusterNode .is(RedisClusterNode.NodeFlag.SLAVE) && redisClusterNode.getSlaveOf().equals(master.get().getNodeId())); assertThat(nodes.size()).isEqualTo(1); commands.set(key, value); waitForReplication(key, port4); Executions<String> keys = nodes.commands().get(key); assertThat(keys).hasSize(1).contains(value); } protected void waitForReplication(String key, int port) throws Exception { waitForReplication(commands, key, port); } protected static void waitForReplication(RedisAdvancedClusterCommands<String, String> commands, String key, int port) throws Exception { NodeSelection<String, String> selection = commands .slaves(redisClusterNode -> redisClusterNode.getUri().getPort() == port); Wait.untilNotEquals(null, () -> { Executions<String> strings = selection.commands().get(key); if (strings.stream().filter(s -> s != null).findFirst().isPresent()) { return "OK"; } return null; }).waitOrTimeout(); } }