package com.lambdaworks.redis.cluster;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import rx.Observable;
import com.lambdaworks.RandomKeys;
import com.lambdaworks.redis.*;
import com.lambdaworks.redis.api.sync.RedisCommands;
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.RedisClusterNode;
import com.lambdaworks.redis.codec.Utf8StringCodec;
import com.lambdaworks.redis.commands.rx.RxSyncInvocationHandler;
import com.lambdaworks.redis.internal.LettuceLists;
/**
* @author Mark Paluch
*/
public class AdvancedClusterReactiveTest extends AbstractClusterTest {
public static final String KEY_ON_NODE_1 = "a";
public static final String KEY_ON_NODE_2 = "b";
private RedisAdvancedClusterReactiveCommands<String, String> commands;
private RedisCommands<String, String> syncCommands;
@Before
public void before() throws Exception {
commands = clusterClient.connectClusterAsync().getStatefulConnection().reactive();
syncCommands = RxSyncInvocationHandler.sync(commands.getStatefulConnection());
}
@After
public void after() throws Exception {
commands.close();
}
@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 doWeirdThingsWithClusterconnections() throws Exception {
assertThat(clusterClient.getPartitions()).hasSize(4);
for (RedisClusterNode redisClusterNode : clusterClient.getPartitions()) {
RedisClusterReactiveCommands<String, String> nodeConnection = commands.getConnection(redisClusterNode.getNodeId());
nodeConnection.close();
RedisClusterReactiveCommands<String, String> nextConnection = commands.getConnection(redisClusterNode.getNodeId());
assertThat(commands).isNotSameAs(nextConnection);
}
}
@Test
public void msetCrossSlot() throws Exception {
Observable<String> mset = commands.mset(RandomKeys.MAP);
List<String> result = LettuceLists.newList(mset.toBlocking().toIterable());
assertThat(result).hasSize(1).contains("OK");
for (String mykey : RandomKeys.KEYS) {
String s1 = syncCommands.get(mykey);
assertThat(s1).isEqualTo(RandomKeys.MAP.get(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(LettuceLists.newList(commands.msetnx(submap).toBlocking().toIterable())).hasSize(1).contains(true);
assertThat(LettuceLists.newList(commands.msetnx(mset).toBlocking().toIterable())).hasSize(1).contains(false);
for (String mykey : mset.keySet()) {
String s1 = syncCommands.get(mykey);
assertThat(s1).isEqualTo(mset.get(mykey));
}
}
@Test
public void mgetCrossSlot() throws Exception {
msetCrossSlot();
Map<Integer, List<String>> partitioned = SlotHash.partition(new Utf8StringCodec(), RandomKeys.KEYS);
assertThat(partitioned.size()).isGreaterThan(100);
Observable<String> observable = commands.mget(RandomKeys.KEYS.toArray(new String[RandomKeys.COUNT]));
List<String> result = observable.toList().toBlocking().single();
assertThat(result).hasSize(RandomKeys.COUNT);
assertThat(result).isEqualTo(RandomKeys.VALUES);
}
@Test
public void mgetCrossSlotStreaming() throws Exception {
msetCrossSlot();
ListStreamingAdapter<String> result = new ListStreamingAdapter<>();
Observable<Long> observable = commands.mget(result, RandomKeys.KEYS.toArray(new String[RandomKeys.COUNT]));
Long count = getSingle(observable);
assertThat(result.getList()).hasSize(RandomKeys.COUNT);
assertThat(count).isEqualTo(RandomKeys.COUNT);
}
@Test
public void delCrossSlot() throws Exception {
msetCrossSlot();
Observable<Long> observable = commands.del(RandomKeys.KEYS.toArray(new String[RandomKeys.COUNT]));
Long result = getSingle(observable);
assertThat(result).isEqualTo(RandomKeys.COUNT);
for (String mykey : RandomKeys.KEYS) {
String s1 = syncCommands.get(mykey);
assertThat(s1).isNull();
}
}
@Test
public void unlinkCrossSlot() throws Exception {
msetCrossSlot();
Observable<Long> observable = commands.unlink(RandomKeys.KEYS.toArray(new String[RandomKeys.COUNT]));
Long result = getSingle(observable);
assertThat(result).isEqualTo(RandomKeys.COUNT);
for (String mykey : RandomKeys.KEYS) {
String s1 = syncCommands.get(mykey);
assertThat(s1).isNull();
}
}
@Test
public void clientSetname() throws Exception {
String name = "test-cluster-client";
assertThat(clusterClient.getPartitions().size()).isGreaterThan(0);
getSingle(commands.clientSetname(name));
for (RedisClusterNode redisClusterNode : clusterClient.getPartitions()) {
RedisClusterCommands<String, String> nodeConnection = commands.getStatefulConnection().sync()
.getConnection(redisClusterNode.getNodeId());
assertThat(nodeConnection.clientList()).contains(name);
}
}
@Test(expected = Exception.class)
public void clientSetnameRunOnError() throws Exception {
getSingle(commands.clientSetname("not allowed"));
}
@Test
public void dbSize() throws Exception {
writeKeysToTwoNodes();
Long dbsize = getSingle(commands.dbsize());
assertThat(dbsize).isEqualTo(2);
}
@Test
public void flushall() throws Exception {
writeKeysToTwoNodes();
assertThat(getSingle(commands.flushall())).isEqualTo("OK");
Long dbsize = syncCommands.dbsize();
assertThat(dbsize).isEqualTo(0);
}
@Test
public void flushdb() throws Exception {
writeKeysToTwoNodes();
assertThat(getSingle(commands.flushdb())).isEqualTo("OK");
Long dbsize = syncCommands.dbsize();
assertThat(dbsize).isEqualTo(0);
}
@Test
public void keys() throws Exception {
writeKeysToTwoNodes();
List<String> result = commands.keys("*").toList().toBlocking().single();
assertThat(result).contains(KEY_ON_NODE_1, KEY_ON_NODE_2);
}
@Test
public void keysStreaming() throws Exception {
writeKeysToTwoNodes();
ListStreamingAdapter<String> result = new ListStreamingAdapter<>();
assertThat(getSingle(commands.keys(result, "*"))).isEqualTo(2);
assertThat(result.getList()).contains(KEY_ON_NODE_1, KEY_ON_NODE_2);
}
@Test
public void randomKey() throws Exception {
writeKeysToTwoNodes();
assertThat(getSingle(commands.randomkey())).isIn(KEY_ON_NODE_1, KEY_ON_NODE_2);
}
@Test
public void scriptFlush() throws Exception {
assertThat(getSingle(commands.scriptFlush())).isEqualTo("OK");
}
@Test
public void scriptKill() throws Exception {
assertThat(getSingle(commands.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 {
commands.shutdown(true).subscribe();
}
@Test
public void readFromSlaves() throws Exception {
RedisClusterReactiveCommands<String, String> connection = commands.getConnection(host, port4);
connection.readOnly().toBlocking().first();
commands.set(key, value).toBlocking().first();
NodeSelectionAsyncTest.waitForReplication(commands.getStatefulConnection().async(), key, port4);
AtomicBoolean error = new AtomicBoolean();
connection.get(key).doOnError(throwable -> error.set(true)).toBlocking().toFuture().get();
assertThat(error.get()).isFalse();
connection.readWrite().toBlocking().first();
try {
connection.get(key).doOnError(throwable -> error.set(true)).toBlocking().first();
fail("Missing exception");
} catch (Exception e) {
assertThat(error.get()).isTrue();
}
}
@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 = getSingle(commands.scan());
} else {
scanCursor = getSingle(commands.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 = getSingle(commands.scan(ScanArgs.Builder.matches("a*")));
} else {
scanCursor = getSingle(commands.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 = getSingle(commands.scan(adapter));
} else {
scanCursor = getSingle(commands.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 = getSingle(commands.scan(adapter, ScanArgs.Builder.matches("a*")));
} else {
scanCursor = getSingle(commands.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()));
}
private <T> T getSingle(Observable<T> observable) {
return observable.toBlocking().single();
}
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;
}
}