package com.lambdaworks.redis.cluster;
import static com.lambdaworks.redis.ScriptOutputType.STATUS;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import com.google.code.tempusfugit.temporal.WaitFor;
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.api.async.RedisAsyncCommands;
import com.lambdaworks.redis.cluster.api.StatefulRedisClusterConnection;
import com.lambdaworks.redis.cluster.api.async.AsyncExecutions;
import com.lambdaworks.redis.cluster.api.async.AsyncNodeSelection;
import com.lambdaworks.redis.cluster.api.async.RedisAdvancedClusterAsyncCommands;
import com.lambdaworks.redis.cluster.api.async.RedisClusterAsyncCommands;
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 NodeSelectionAsyncTest extends AbstractClusterTest {
private RedisAdvancedClusterAsyncCommands<String, String> commands;
private RedisAdvancedClusterCommands<String, String> syncCommands;
private StatefulRedisClusterConnection<String, String> clusterConnection;
@Before
public void before() throws Exception {
clusterClient.reloadPartitions();
clusterConnection = clusterClient.connect();
commands = clusterConnection.async();
syncCommands = 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).get();
}
List<String> result = new Vector<>();
CompletableFuture.allOf(commands.masters().commands().keys(result::add, "*").futures()).get();
assertThat(result).hasSize(expectation.size());
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 {
AsyncNodeSelection<String, String> onlyMe = commands.nodes(redisClusterNode -> redisClusterNode.getFlags().contains(
RedisClusterNode.NodeFlag.MYSELF));
Map<RedisClusterNode, RedisAsyncCommands<String, String>> map = onlyMe.asMap();
assertThat(map).hasSize(1);
RedisClusterAsyncCommands<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)));
AsyncNodeSelection<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 testNodeSelectionAsyncPing() throws Exception {
AsyncNodeSelection<String, String> onlyMe = commands.nodes(redisClusterNode -> redisClusterNode.getFlags().contains(
RedisClusterNode.NodeFlag.MYSELF));
Map<RedisClusterNode, RedisAsyncCommands<String, String>> map = onlyMe.asMap();
assertThat(map).hasSize(1);
AsyncExecutions<String> ping = onlyMe.commands().ping();
CompletionStage<String> completionStage = ping.get(onlyMe.node(0));
assertThat(completionStage.toCompletableFuture().get()).isEqualTo("PONG");
}
@Test
public void testStaticNodeSelection() throws Exception {
AsyncNodeSelection<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 {
RedisAdvancedClusterAsyncCommands<String, String> connection2 = clusterClient.connectClusterAsync();
AsyncNodeSelection<String, String> masters = connection2.masters();
CompletableFuture.allOf(masters.commands().configSet("lua-time-limit", "10").futures()).get();
AsyncExecutions<Object> eval = masters.commands().eval("while true do end", STATUS, new String[0]);
for (CompletableFuture<Object> future : eval.futures()) {
assertThat(future.isDone()).isFalse();
assertThat(future.isCancelled()).isFalse();
}
Thread.sleep(200);
AsyncExecutions<String> kill = commands.masters().commands().scriptKill();
CompletableFuture.allOf(kill.futures()).get();
for (CompletionStage<String> execution : kill) {
assertThat(execution.toCompletableFuture().get()).isEqualTo("OK");
}
CompletableFuture.allOf(eval.futures()).exceptionally(throwable -> null).get();
for (CompletableFuture<Object> future : eval.futures()) {
assertThat(future.isDone()).isTrue();
}
}
@Test
public void testSlavesReadWrite() throws Exception {
AsyncNodeSelection<String, String> nodes = commands.nodes(redisClusterNode -> redisClusterNode.getFlags().contains(
RedisClusterNode.NodeFlag.SLAVE));
assertThat(nodes.size()).isEqualTo(2);
commands.set(key, value).get();
waitForReplication(key, port4);
List<Throwable> t = new ArrayList<>();
AsyncExecutions<String> keys = nodes.commands().get(key);
keys.stream().forEach(lcs -> {
lcs.toCompletableFuture().exceptionally(throwable -> {
t.add(throwable);
return null;
});
});
CompletableFuture.allOf(keys.futures()).exceptionally(throwable -> null).get();
assertThat(t.size()).isGreaterThan(0);
}
@Test
public void testSlavesWithReadOnly() throws Exception {
AsyncNodeSelection<String, String> nodes = commands.slaves(redisClusterNode -> redisClusterNode
.is(RedisClusterNode.NodeFlag.SLAVE));
assertThat(nodes.size()).isEqualTo(2);
commands.set(key, value).get();
waitForReplication(key, port4);
List<Throwable> t = new ArrayList<>();
List<String> strings = new ArrayList<>();
AsyncExecutions<String> keys = nodes.commands().get(key);
keys.stream().forEach(lcs -> {
lcs.toCompletableFuture().exceptionally(throwable -> {
t.add(throwable);
return null;
});
lcs.thenAccept(strings::add);
});
CompletableFuture.allOf(keys.futures()).exceptionally(throwable -> null).get();
Wait.untilEquals(1, () -> t.size()).waitOrTimeout();
assertThat(t).hasSize(1);
assertThat(strings).hasSize(1).contains(value);
}
protected void waitForReplication(String key, int port) throws Exception {
waitForReplication(commands, key, port);
}
protected static void waitForReplication(RedisAdvancedClusterAsyncCommands<String, String> commands, String key, int port)
throws Exception {
AsyncNodeSelection<String, String> selection = commands
.slaves(redisClusterNode -> redisClusterNode.getUri().getPort() == port);
Wait.untilNotEquals(null, () -> {
for (CompletableFuture<String> future : selection.commands().get(key).futures()) {
if (future.get() != null) {
return future.get();
}
}
return null;
}).waitOrTimeout();
}
}