package com.lambdaworks.redis.cluster;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import com.lambdaworks.RandomKeys;
import com.lambdaworks.redis.*;
import com.lambdaworks.redis.api.StatefulRedisConnection;
import com.lambdaworks.redis.cluster.api.StatefulRedisClusterConnection;
import com.lambdaworks.redis.cluster.api.async.RedisAdvancedClusterAsyncCommands;
import com.lambdaworks.redis.cluster.api.rx.RedisAdvancedClusterReactiveCommands;
import com.lambdaworks.redis.cluster.api.rx.RedisClusterReactiveCommands;
import com.lambdaworks.redis.cluster.api.sync.RedisAdvancedClusterCommands;
import com.lambdaworks.redis.cluster.api.sync.RedisClusterCommands;
import com.lambdaworks.redis.cluster.models.partitions.Partitions;
import com.lambdaworks.redis.cluster.models.partitions.RedisClusterNode;
/**
* @author Mark Paluch
*/
@SuppressWarnings("rawtypes")
public class AdvancedClusterClientTest extends AbstractClusterTest {
public static final String KEY_ON_NODE_1 = "a";
public static final String KEY_ON_NODE_2 = "b";
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 nodeConnections() throws Exception {
assertThat(clusterClient.getPartitions()).hasSize(4);
for (RedisClusterNode redisClusterNode : clusterClient.getPartitions()) {
RedisClusterAsyncConnection<String, String> nodeConnection = commands.getConnection(redisClusterNode.getNodeId());
String myid = nodeConnection.clusterMyId().get();
assertThat(myid).isEqualTo(redisClusterNode.getNodeId());
}
}
@Test(expected = RedisException.class)
public void unknownNodeId() throws Exception {
commands.getConnection("unknown");
}
@Test(expected = RedisException.class)
public void invalidHost() throws Exception {
commands.getConnection("invalid-host", -1);
}
@Test
public void partitions() throws Exception {
Partitions partitions = commands.getStatefulConnection().getPartitions();
assertThat(partitions).hasSize(4);
}
@Test
public void doWeirdThingsWithClusterconnections() throws Exception {
assertThat(clusterClient.getPartitions()).hasSize(4);
for (RedisClusterNode redisClusterNode : clusterClient.getPartitions()) {
RedisClusterAsyncConnection<String, String> nodeConnection = commands.getConnection(redisClusterNode.getNodeId());
nodeConnection.close();
RedisClusterAsyncConnection<String, String> nextConnection = commands.getConnection(redisClusterNode.getNodeId());
assertThat(commands).isNotSameAs(nextConnection);
}
}
@Test
public void differentConnections() throws Exception {
for (RedisClusterNode redisClusterNode : clusterClient.getPartitions()) {
RedisClusterAsyncConnection<String, String> nodeId = commands.getConnection(redisClusterNode.getNodeId());
RedisClusterAsyncConnection<String, String> hostAndPort = commands
.getConnection(redisClusterNode.getUri().getHost(), redisClusterNode.getUri().getPort());
assertThat(nodeId).isNotSameAs(hostAndPort);
}
StatefulRedisClusterConnection<String, String> statefulConnection = commands.getStatefulConnection();
for (RedisClusterNode redisClusterNode : clusterClient.getPartitions()) {
StatefulRedisConnection<String, String> nodeId = statefulConnection.getConnection(redisClusterNode.getNodeId());
StatefulRedisConnection<String, String> hostAndPort = statefulConnection
.getConnection(redisClusterNode.getUri().getHost(), redisClusterNode.getUri().getPort());
assertThat(nodeId).isNotSameAs(hostAndPort);
}
RedisAdvancedClusterCommands<String, String> sync = statefulConnection.sync();
for (RedisClusterNode redisClusterNode : clusterClient.getPartitions()) {
RedisClusterCommands<String, String> nodeId = sync.getConnection(redisClusterNode.getNodeId());
RedisClusterCommands<String, String> hostAndPort = sync.getConnection(redisClusterNode.getUri().getHost(),
redisClusterNode.getUri().getPort());
assertThat(nodeId).isNotSameAs(hostAndPort);
}
RedisAdvancedClusterReactiveCommands<String, String> rx = statefulConnection.reactive();
for (RedisClusterNode redisClusterNode : clusterClient.getPartitions()) {
RedisClusterReactiveCommands<String, String> nodeId = rx.getConnection(redisClusterNode.getNodeId());
RedisClusterReactiveCommands<String, String> hostAndPort = rx.getConnection(redisClusterNode.getUri().getHost(),
redisClusterNode.getUri().getPort());
assertThat(nodeId).isNotSameAs(hostAndPort);
}
}
@Test
public void msetRegular() throws Exception {
Map<String, String> mset = Collections.singletonMap(key, value);
String result = syncCommands.mset(mset);
assertThat(result).isEqualTo("OK");
assertThat(syncCommands.get(key)).isEqualTo(value);
}
@Test
public void msetCrossSlot() throws Exception {
Map<String, String> mset = prepareMset();
String result = syncCommands.mset(mset);
assertThat(result).isEqualTo("OK");
for (String mykey : mset.keySet()) {
String s1 = syncCommands.get(mykey);
assertThat(s1).isEqualTo("value-" + mykey);
}
}
@Test
public void msetnxCrossSlot() throws Exception {
Map<String, String> mset = prepareMset();
String key = mset.keySet().iterator().next();
Map<String, String> submap = Collections.singletonMap(key, mset.get(key));
assertThat(commands.msetnx(submap).get()).isTrue();
assertThat(commands.msetnx(mset).get()).isFalse();
for (String mykey : mset.keySet()) {
String s1 = commands.get(mykey).get();
assertThat(s1).isEqualTo("value-" + mykey);
}
}
@Test
public void mgetRegular() throws Exception {
msetRegular();
List<String> result = syncCommands.mget(key);
assertThat(result).hasSize(1);
}
@Test
public void mgetCrossSlot() throws Exception {
msetCrossSlot();
List<String> keys = new ArrayList<>();
List<String> expectation = new ArrayList<>();
for (char c = 'a'; c < 'z'; c++) {
String key = new String(new char[] { c, c, c });
keys.add(key);
expectation.add("value-" + key);
}
List<String> result = syncCommands.mget(keys.toArray(new String[keys.size()]));
assertThat(result).hasSize(keys.size());
assertThat(result).isEqualTo(expectation);
}
@Test
public void delRegular() throws Exception {
msetRegular();
Long result = syncCommands.unlink(key);
assertThat(result).isEqualTo(1);
assertThat(commands.get(key).get()).isNull();
}
@Test
public void delCrossSlot() throws Exception {
List<String> keys = prepareKeys();
Long result = syncCommands.del(keys.toArray(new String[keys.size()]));
assertThat(result).isEqualTo(25);
for (String mykey : keys) {
String s1 = syncCommands.get(mykey);
assertThat(s1).isNull();
}
}
@Test
public void unlinkRegular() throws Exception {
msetRegular();
Long result = syncCommands.unlink(key);
assertThat(result).isEqualTo(1);
assertThat(syncCommands.get(key)).isNull();
}
@Test
public void unlinkCrossSlot() throws Exception {
List<String> keys = prepareKeys();
Long result = syncCommands.unlink(keys.toArray(new String[keys.size()]));
assertThat(result).isEqualTo(25);
for (String mykey : keys) {
String s1 = syncCommands.get(mykey);
assertThat(s1).isNull();
}
}
protected List<String> prepareKeys() throws Exception {
msetCrossSlot();
List<String> keys = new ArrayList<>();
for (char c = 'a'; c < 'z'; c++) {
String key = new String(new char[] { c, c, c });
keys.add(key);
}
return keys;
}
@Test
public void clientSetname() throws Exception {
String name = "test-cluster-client";
assertThat(clusterClient.getPartitions().size()).isGreaterThan(0);
syncCommands.clientSetname(name);
for (RedisClusterNode redisClusterNode : clusterClient.getPartitions()) {
RedisClusterCommands<String, String> nodeConnection = commands.getStatefulConnection().sync()
.getConnection(redisClusterNode.getNodeId());
assertThat(nodeConnection.clientList()).contains(name);
}
}
@Test(expected = RedisCommandExecutionException.class)
public void clientSetnameRunOnError() throws Exception {
syncCommands.clientSetname("not allowed");
}
@Test
public void dbSize() throws Exception {
writeKeysToTwoNodes();
RedisClusterCommands<String, String> nodeConnection1 = clusterConnection.getConnection(host, port1).sync();
RedisClusterCommands<String, String> nodeConnection2 = clusterConnection.getConnection(host, port2).sync();
assertThat(nodeConnection1.dbsize()).isEqualTo(1);
assertThat(nodeConnection2.dbsize()).isEqualTo(1);
Long dbsize = syncCommands.dbsize();
assertThat(dbsize).isEqualTo(2);
}
@Test
public void flushall() throws Exception {
writeKeysToTwoNodes();
assertThat(syncCommands.flushall()).isEqualTo("OK");
Long dbsize = syncCommands.dbsize();
assertThat(dbsize).isEqualTo(0);
}
@Test
public void flushdb() throws Exception {
writeKeysToTwoNodes();
assertThat(syncCommands.flushdb()).isEqualTo("OK");
Long dbsize = syncCommands.dbsize();
assertThat(dbsize).isEqualTo(0);
}
@Test
public void keys() throws Exception {
writeKeysToTwoNodes();
assertThat(syncCommands.keys("*")).contains(KEY_ON_NODE_1, KEY_ON_NODE_2);
}
@Test
public void keysStreaming() throws Exception {
writeKeysToTwoNodes();
ListStreamingAdapter<String> result = new ListStreamingAdapter<>();
assertThat(syncCommands.keys(result, "*")).isEqualTo(2);
assertThat(result.getList()).contains(KEY_ON_NODE_1, KEY_ON_NODE_2);
}
@Test
public void randomKey() throws Exception {
writeKeysToTwoNodes();
assertThat(syncCommands.randomkey()).isIn(KEY_ON_NODE_1, KEY_ON_NODE_2);
}
@Test
public void scriptFlush() throws Exception {
assertThat(syncCommands.scriptFlush()).isEqualTo("OK");
}
@Test
public void scriptKill() throws Exception {
assertThat(syncCommands.scriptKill()).isEqualTo("OK");
}
@Test
@Ignore("Run me manually, I will shutdown all your cluster nodes so you need to restart the Redis Cluster after this test")
public void shutdown() throws Exception {
syncCommands.shutdown(true);
}
@Test
public void testSync() throws Exception {
RedisAdvancedClusterCommands<String, String> sync = commands.getStatefulConnection().sync();
sync.set(key, value);
assertThat(sync.get(key)).isEqualTo(value);
RedisClusterCommands<String, String> node2Connection = sync.getConnection(host, port2);
assertThat(node2Connection.get(key)).isEqualTo(value);
assertThat(sync.getStatefulConnection()).isSameAs(commands.getStatefulConnection());
}
@Test
public void routeCommandTonoAddrPartition() throws Exception {
RedisClusterCommands<String, String> sync = clusterClient.connect().sync();
try {
Partitions partitions = clusterClient.getPartitions();
for (RedisClusterNode partition : partitions) {
partition.setUri(RedisURI.create("redis://non.existent.host:1234"));
}
sync.set("A", "value");// 6373
} catch (Exception e) {
assertThat(e).isInstanceOf(RedisException.class).hasMessageContaining("Unable to connect to");
} finally {
clusterClient.getPartitions().clear();
clusterClient.reloadPartitions();
}
sync.close();
}
@Test
public void routeCommandToForbiddenHostOnRedirect() throws Exception {
RedisClusterCommands<String, String> sync = clusterClient.connect().sync();
try {
Partitions partitions = clusterClient.getPartitions();
for (RedisClusterNode partition : partitions) {
partition.setSlots(Collections.singletonList(0));
if (partition.getUri().getPort() == 7380) {
partition.setSlots(Collections.singletonList(6373));
} else {
partition.setUri(RedisURI.create("redis://non.existent.host:1234"));
}
}
partitions.updateCache();
sync.set("A", "value");// 6373
} catch (Exception e) {
assertThat(e).isInstanceOf(RedisException.class).hasMessageContaining("not allowed");
} finally {
clusterClient.getPartitions().clear();
clusterClient.reloadPartitions();
}
sync.close();
}
@Test
public void getConnectionToNotAClusterMemberForbidden() throws Exception {
RedisAdvancedClusterConnection<String, String> sync = clusterClient.connectCluster();
try {
sync.getConnection(TestSettings.host(), TestSettings.port());
} catch (RedisException e) {
assertThat(e).hasRootCauseExactlyInstanceOf(IllegalArgumentException.class);
}
sync.close();
}
@Test
public void getConnectionToNotAClusterMemberAllowed() throws Exception {
clusterClient.setOptions(ClusterClientOptions.builder().validateClusterNodeMembership(false).build());
StatefulRedisClusterConnection<String, String> connection = clusterClient.connect();
connection.getConnection(TestSettings.host(), TestSettings.port());
connection.close();
}
@Test
public void pipelining() throws Exception {
RedisClusterCommands<String, String> verificationConnection = clusterClient.connect().sync();
// preheat the first connection
commands.get(key(0)).get();
int iterations = 1000;
commands.setAutoFlushCommands(false);
List<RedisFuture<?>> futures = new ArrayList<>();
for (int i = 0; i < iterations; i++) {
futures.add(commands.set(key(i), value(i)));
}
for (int i = 0; i < iterations; i++) {
assertThat(verificationConnection.get(key(i))).as("Key " + key(i) + " must be null").isNull();
}
commands.flushCommands();
boolean result = LettuceFutures.awaitAll(5, TimeUnit.SECONDS, futures.toArray(new RedisFuture[futures.size()]));
assertThat(result).isTrue();
for (int i = 0; i < iterations; i++) {
assertThat(verificationConnection.get(key(i))).as("Key " + key(i) + " must be " + value(i)).isEqualTo(value(i));
}
verificationConnection.close();
}
@Test
public void transactions() throws Exception {
commands.multi();
commands.set(key, value);
commands.discard();
commands.multi();
commands.set(key, value);
commands.exec();
}
@Test
public void clusterScan() throws Exception {
RedisAdvancedClusterCommands<String, String> sync = commands.getStatefulConnection().sync();
sync.mset(RandomKeys.MAP);
Set<String> allKeys = new HashSet<>();
KeyScanCursor<String> scanCursor = null;
do {
if (scanCursor == null) {
scanCursor = sync.scan();
} else {
scanCursor = sync.scan(scanCursor);
}
allKeys.addAll(scanCursor.getKeys());
} while (!scanCursor.isFinished());
assertThat(allKeys).containsAll(RandomKeys.KEYS);
}
@Test
public void clusterScanWithArgs() throws Exception {
RedisAdvancedClusterCommands<String, String> sync = commands.getStatefulConnection().sync();
sync.mset(RandomKeys.MAP);
Set<String> allKeys = new HashSet<>();
KeyScanCursor<String> scanCursor = null;
do {
if (scanCursor == null) {
scanCursor = sync.scan(ScanArgs.Builder.matches("a*"));
} else {
scanCursor = sync.scan(scanCursor, ScanArgs.Builder.matches("a*"));
}
allKeys.addAll(scanCursor.getKeys());
} while (!scanCursor.isFinished());
assertThat(allKeys).containsAll(RandomKeys.KEYS.stream().filter(k -> k.startsWith("a")).collect(Collectors.toList()));
}
@Test
public void clusterScanStreaming() throws Exception {
RedisAdvancedClusterCommands<String, String> sync = commands.getStatefulConnection().sync();
sync.mset(RandomKeys.MAP);
ListStreamingAdapter<String> adapter = new ListStreamingAdapter<>();
StreamScanCursor scanCursor = null;
do {
if (scanCursor == null) {
scanCursor = sync.scan(adapter);
} else {
scanCursor = sync.scan(adapter, scanCursor);
}
} while (!scanCursor.isFinished());
assertThat(adapter.getList()).containsAll(RandomKeys.KEYS);
}
@Test
public void clusterScanStreamingWithArgs() throws Exception {
RedisAdvancedClusterCommands<String, String> sync = commands.getStatefulConnection().sync();
sync.mset(RandomKeys.MAP);
ListStreamingAdapter<String> adapter = new ListStreamingAdapter<>();
StreamScanCursor scanCursor = null;
do {
if (scanCursor == null) {
scanCursor = sync.scan(adapter, ScanArgs.Builder.matches("a*"));
} else {
scanCursor = sync.scan(adapter, scanCursor, ScanArgs.Builder.matches("a*"));
}
} while (!scanCursor.isFinished());
assertThat(adapter.getList())
.containsAll(RandomKeys.KEYS.stream().filter(k -> k.startsWith("a")).collect(Collectors.toList()));
}
@Test(expected = IllegalArgumentException.class)
public void clusterScanCursorFinished() throws Exception {
syncCommands.scan(ScanCursor.FINISHED);
}
@Test(expected = IllegalArgumentException.class)
public void clusterScanCursorNotReused() throws Exception {
syncCommands.scan(ScanCursor.of("dummy"));
}
protected String value(int i) {
return value + "-" + i;
}
protected String key(int i) {
return key + "-" + i;
}
private void writeKeysToTwoNodes() {
syncCommands.set(KEY_ON_NODE_1, value);
syncCommands.set(KEY_ON_NODE_2, value);
}
protected Map<String, String> prepareMset() {
Map<String, String> mset = new HashMap<>();
for (char c = 'a'; c < 'z'; c++) {
String key = new String(new char[] { c, c, c });
mset.put(key, "value-" + key);
}
return mset;
}
}