package com.lambdaworks.redis.cluster;
import static com.lambdaworks.redis.RedisClientConnectionTest.CODEC;
import static com.lambdaworks.redis.cluster.ClusterTestUtil.getOwnPartition;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.assertj.core.api.AssertionsForClassTypes;
import org.junit.*;
import org.junit.runners.MethodSorters;
import org.springframework.test.util.ReflectionTestUtils;
import com.lambdaworks.redis.*;
import com.lambdaworks.redis.api.StatefulConnection;
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.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;
import com.lambdaworks.redis.protocol.AsyncCommand;
import com.lambdaworks.redis.pubsub.StatefulRedisPubSubConnection;
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@SuppressWarnings("unchecked")
public class RedisClusterClientTest extends AbstractClusterTest {
protected static RedisClient client;
protected StatefulRedisConnection<String, String> redis1;
protected StatefulRedisConnection<String, String> redis2;
protected StatefulRedisConnection<String, String> redis3;
protected StatefulRedisConnection<String, String> redis4;
protected RedisClusterCommands<String, String> redissync1;
protected RedisClusterCommands<String, String> redissync2;
protected RedisClusterCommands<String, String> redissync3;
protected RedisClusterCommands<String, String> redissync4;
protected RedisAdvancedClusterCommands<String, String> sync;
@BeforeClass
public static void setupClient() throws Exception {
setupClusterClient();
client = RedisClient.create(RedisURI.Builder.redis(host, port1).build());
clusterClient = RedisClusterClient.create(Collections.singletonList(RedisURI.Builder.redis(host, port1).build()));
}
@AfterClass
public static void shutdownClient() {
shutdownClusterClient();
FastShutdown.shutdown(client);
FastShutdown.shutdown(clusterClient);
}
@Before
public void before() throws Exception {
clusterClient.setOptions(ClusterClientOptions.create());
clusterRule.getClusterClient().reloadPartitions();
redis1 = client.connect(RedisURI.Builder.redis(host, port1).build());
redis2 = client.connect(RedisURI.Builder.redis(host, port2).build());
redis3 = client.connect(RedisURI.Builder.redis(host, port3).build());
redis4 = client.connect(RedisURI.Builder.redis(host, port4).build());
redissync1 = redis1.sync();
redissync2 = redis2.sync();
redissync3 = redis3.sync();
redissync4 = redis4.sync();
clusterClient.reloadPartitions();
sync = clusterClient.connectCluster();
}
@After
public void after() throws Exception {
sync.close();
redis1.close();
redissync1.close();
redissync2.close();
redissync3.close();
redissync4.close();
}
@Test
public void statefulConnectionFromSync() throws Exception {
RedisAdvancedClusterConnection<String, String> sync = clusterClient.connectCluster();
assertThat(sync.getStatefulConnection().sync()).isSameAs(sync);
sync.close();
}
@Test
public void statefulConnectionFromAsync() throws Exception {
RedisAsyncConnection<String, String> async = client.connectAsync();
assertThat(async.getStatefulConnection().async()).isSameAs(async);
async.close();
}
@Test
public void shouldApplyTimeoutOnRegularConnection() throws Exception {
clusterClient.setDefaultTimeout(1, TimeUnit.MINUTES);
StatefulRedisClusterConnection<String, String> connection = clusterClient.connect();
assertTimeout(connection, 1, TimeUnit.MINUTES);
assertTimeout(connection.getConnection(host, port1), 1, TimeUnit.MINUTES);
connection.close();
}
@Test
public void shouldApplyTimeoutOnRegularConnectionUsingCodec() throws Exception {
clusterClient.setDefaultTimeout(1, TimeUnit.MINUTES);
StatefulRedisClusterConnection<String, String> connection = clusterClient.connect(CODEC);
assertTimeout(connection, 1, TimeUnit.MINUTES);
assertTimeout(connection.getConnection(host, port1), 1, TimeUnit.MINUTES);
connection.close();
}
@Test
public void shouldApplyTimeoutOnPubSubConnection() throws Exception {
clusterClient.setDefaultTimeout(1, TimeUnit.MINUTES);
StatefulRedisPubSubConnection<String, String> connection = clusterClient.connectPubSub();
assertTimeout(connection, 1, TimeUnit.MINUTES);
connection.close();
}
@Test
public void shouldApplyTimeoutOnPubSubConnectionUsingCodec() throws Exception {
clusterClient.setDefaultTimeout(1, TimeUnit.MINUTES);
StatefulRedisPubSubConnection<String, String> connection = clusterClient.connectPubSub(CODEC);
assertTimeout(connection, 1, TimeUnit.MINUTES);
connection.close();
}
@Test
public void reloadPartitions() throws Exception {
assertThat(clusterClient.getPartitions()).hasSize(4);
assertThat(clusterClient.getPartitions().getPartition(0).getUri());
assertThat(clusterClient.getPartitions().getPartition(1).getUri());
assertThat(clusterClient.getPartitions().getPartition(2).getUri());
assertThat(clusterClient.getPartitions().getPartition(3).getUri());
clusterClient.reloadPartitions();
assertThat(clusterClient.getPartitions().getPartition(0).getUri());
assertThat(clusterClient.getPartitions().getPartition(1).getUri());
assertThat(clusterClient.getPartitions().getPartition(2).getUri());
assertThat(clusterClient.getPartitions().getPartition(3).getUri());
}
@Test
public void testClusteredOperations() throws Exception {
SlotHash.getSlot(KEY_B.getBytes()); // 3300 -> Node 1 and Slave (Node 3)
SlotHash.getSlot(KEY_A.getBytes()); // 15495 -> Node 2
RedisFuture<String> result = redis1.async().set(KEY_B, value);
assertThat(result.getError()).isEqualTo(null);
assertThat(redissync1.set(KEY_B, "value")).isEqualTo("OK");
RedisFuture<String> resultMoved = redis1.async().set(KEY_A, value);
try {
resultMoved.get();
} catch (Exception e) {
assertThat(e.getMessage()).contains("MOVED 15495");
}
clusterClient.reloadPartitions();
RedisClusterAsyncConnection<String, String> connection = clusterClient.connectClusterAsync();
RedisFuture<String> setA = connection.set(KEY_A, value);
setA.get();
assertThat(setA.getError()).isNull();
assertThat(setA.get()).isEqualTo("OK");
RedisFuture<String> setB = connection.set(KEY_B, "myValue2");
assertThat(setB.get()).isEqualTo("OK");
RedisFuture<String> setD = connection.set("d", "myValue2");
assertThat(setD.get()).isEqualTo("OK");
connection.close();
}
@Test
public void testReset() throws Exception {
clusterClient.reloadPartitions();
RedisAdvancedClusterAsyncCommandsImpl<String, String> connection = (RedisAdvancedClusterAsyncCommandsImpl) clusterClient
.connectClusterAsync();
RedisFuture<String> setA = connection.set(KEY_A, value);
setA.get();
connection.reset();
setA = connection.set(KEY_A, "myValue1");
assertThat(setA.getError()).isNull();
assertThat(setA.get()).isEqualTo("OK");
connection.close();
}
@Test
@SuppressWarnings({ "rawtypes" })
public void testClusterCommandRedirection() throws Exception {
RedisAdvancedClusterAsyncCommands<String, String> connection = clusterClient.connect().async();
// Command on node within the default connection
assertThat(connection.set(KEY_B, value).get()).isEqualTo("OK");
// gets redirection to node 3
assertThat(connection.set(KEY_A, value).get()).isEqualTo("OK");
connection.close();
}
@Test
@SuppressWarnings({ "rawtypes" })
public void testClusterRedirection() throws Exception {
RedisAdvancedClusterAsyncCommands<String, String> connection = clusterClient.connect().async();
Partitions partitions = clusterClient.getPartitions();
for (RedisClusterNode partition : partitions) {
partition.setSlots(new ArrayList<>());
if (partition.getFlags().contains(RedisClusterNode.NodeFlag.MYSELF)) {
int[] slots = createSlots(0, 16384);
for (int i = 0; i < slots.length; i++) {
partition.getSlots().add(i);
}
}
}
partitions.updateCache();
// appropriate cluster node
RedisFuture<String> setB = connection.set(KEY_B, value);
assertThat(setB).isInstanceOf(AsyncCommand.class);
setB.get(10, TimeUnit.SECONDS);
assertThat(setB.getError()).isNull();
assertThat(setB.get()).isEqualTo("OK");
// gets redirection to node 3
RedisFuture<String> setA = connection.set(KEY_A, value);
assertThat(setA instanceof AsyncCommand).isTrue();
setA.get(10, TimeUnit.SECONDS);
assertThat(setA.getError()).isNull();
assertThat(setA.get()).isEqualTo("OK");
connection.close();
}
@Test
@SuppressWarnings({ "rawtypes" })
public void testClusterRedirectionLimit() throws Exception {
clusterClient.setOptions(ClusterClientOptions.builder().maxRedirects(0).build());
RedisAdvancedClusterAsyncCommands<String, String> connection = clusterClient.connect().async();
Partitions partitions = clusterClient.getPartitions();
for (RedisClusterNode partition : partitions) {
if (partition.getSlots().contains(15495)) {
partition.setSlots(new ArrayList<>());
} else {
partition.setSlots(new ArrayList<>());
int[] slots = createSlots(0, 16384);
for (int i = 0; i < slots.length; i++) {
partition.getSlots().add(i);
}
}
}
partitions.updateCache();
// gets redirection to node 3
RedisFuture<String> setA = connection.set(KEY_A, value);
assertThat(setA instanceof AsyncCommand).isTrue();
setA.await(10, TimeUnit.SECONDS);
assertThat(setA.getError()).isEqualTo("MOVED 15495 127.0.0.1:7380");
connection.close();
}
@Test(expected = RedisException.class)
public void closeConnection() throws Exception {
try (RedisAdvancedClusterCommands<String, String> connection = clusterClient.connect().sync()) {
List<String> time = connection.time();
assertThat(time).hasSize(2);
connection.close();
connection.time();
}
}
@Test
public void clusterAuth() throws Exception {
RedisClusterClient clusterClient = new RedisClusterClient(
RedisURI.Builder.redis(TestSettings.host(), port7).withPassword("foobared").build());
try (RedisAdvancedClusterConnection<String, String> connection = clusterClient.connectCluster()) {
List<String> time = connection.time();
assertThat(time).hasSize(2);
connection.getStatefulConnection().async().quit().get();
time = connection.time();
assertThat(time).hasSize(2);
char[] password = (char[]) ReflectionTestUtils.getField(connection.getStatefulConnection(), "password");
assertThat(new String(password)).isEqualTo("foobared");
} finally {
FastShutdown.shutdown(clusterClient);
}
}
@Test(expected = RedisException.class)
public void clusterNeedsAuthButNotSupplied() throws Exception {
RedisClusterClient clusterClient = new RedisClusterClient(RedisURI.Builder.redis(TestSettings.host(), port7).build());
try (RedisClusterCommands<String, String> connection = clusterClient.connectCluster()) {
List<String> time = connection.time();
assertThat(time).hasSize(2);
} finally {
FastShutdown.shutdown(clusterClient);
}
}
@Test
public void noClusterNodeAvailable() throws Exception {
RedisClusterClient clusterClient = new RedisClusterClient(RedisURI.Builder.redis(host, 40400).build());
try {
clusterClient.connectCluster();
fail("Missing RedisException");
} catch (RedisException e) {
assertThat(e).isInstanceOf(RedisException.class);
}
}
@Test
public void getClusterNodeConnection() throws Exception {
RedisClusterNode redis1Node = getOwnPartition(redissync2);
RedisClusterCommands<String, String> connection = sync.getConnection(TestSettings.hostAddr(), port2);
String result = connection.clusterMyId();
assertThat(result).isEqualTo(redis1Node.getNodeId());
}
@Test
public void operateOnNodeConnection() throws Exception {
sync.set(KEY_A, value);
sync.set(KEY_B, "d");
StatefulRedisConnection<String, String> statefulRedisConnection = sync.getStatefulConnection()
.getConnection(TestSettings.hostAddr(), port2);
RedisClusterCommands<String, String> connection = statefulRedisConnection.sync();
assertThat(connection.get(KEY_A)).isEqualTo(value);
try {
connection.get(KEY_B);
fail("missing RedisCommandExecutionException: MOVED");
} catch (RedisException e) {
assertThat(e).hasMessageContaining("MOVED");
}
}
@Test
public void testStatefulConnection() throws Exception {
RedisAdvancedClusterAsyncCommands<String, String> async = sync.getStatefulConnection().async();
assertThat(async.ping().get()).isEqualTo("PONG");
}
@Test(expected = RedisException.class)
public void getButNoPartitionForSlothash() throws Exception {
for (RedisClusterNode redisClusterNode : clusterClient.getPartitions()) {
redisClusterNode.setSlots(new ArrayList<>());
}
RedisChannelHandler rch = (RedisChannelHandler) sync.getStatefulConnection();
ClusterDistributionChannelWriter<String, String> writer = (ClusterDistributionChannelWriter<String, String>) rch
.getChannelWriter();
writer.setPartitions(clusterClient.getPartitions());
clusterClient.getPartitions().reload(clusterClient.getPartitions().getPartitions());
sync.get(key);
}
@Test
public void readOnlyOnCluster() throws Exception {
sync.readOnly();
// commands are dispatched to a different connection, therefore it works for us.
sync.set(KEY_B, value);
sync.getStatefulConnection().async().quit().get();
assertThat(ReflectionTestUtils.getField(sync.getStatefulConnection(), "readOnly")).isEqualTo(Boolean.TRUE);
sync.readWrite();
assertThat(ReflectionTestUtils.getField(sync.getStatefulConnection(), "readOnly")).isEqualTo(Boolean.FALSE);
RedisClusterClient clusterClient = new RedisClusterClient(RedisURI.Builder.redis(host, 40400).build());
try {
clusterClient.connectCluster();
fail("Missing RedisException");
} catch (RedisException e) {
assertThat(e).isInstanceOf(RedisException.class);
}
}
@Test
public void getKeysInSlot() throws Exception {
sync.set(KEY_A, value);
sync.set(KEY_B, value);
List<String> keysA = sync.clusterGetKeysInSlot(SLOT_A, 10);
assertThat(keysA).isEqualTo(Collections.singletonList(KEY_A));
List<String> keysB = sync.clusterGetKeysInSlot(SLOT_B, 10);
assertThat(keysB).isEqualTo(Collections.singletonList(KEY_B));
}
@Test
public void countKeysInSlot() throws Exception {
sync.set(KEY_A, value);
sync.set(KEY_B, value);
Long result = sync.clusterCountKeysInSlot(SLOT_A);
assertThat(result).isEqualTo(1L);
result = sync.clusterCountKeysInSlot(SLOT_B);
assertThat(result).isEqualTo(1L);
int slotZZZ = SlotHash.getSlot("ZZZ".getBytes());
result = sync.clusterCountKeysInSlot(slotZZZ);
assertThat(result).isEqualTo(0L);
}
@Test
public void testClusterCountFailureReports() throws Exception {
RedisClusterNode ownPartition = getOwnPartition(redissync1);
assertThat(redissync1.clusterCountFailureReports(ownPartition.getNodeId())).isGreaterThanOrEqualTo(0);
}
@Test
public void testClusterKeyslot() throws Exception {
assertThat(redissync1.clusterKeyslot(KEY_A)).isEqualTo(SLOT_A);
assertThat(SlotHash.getSlot(KEY_A)).isEqualTo(SLOT_A);
}
@Test
public void testClusterSaveconfig() throws Exception {
assertThat(redissync1.clusterSaveconfig()).isEqualTo("OK");
}
@Test
public void testClusterSetConfigEpoch() throws Exception {
try {
redissync1.clusterSetConfigEpoch(1L);
} catch (RedisException e) {
assertThat(e).hasMessageContaining("ERR The user can assign a config epoch only");
}
}
@Test
public void testReadFrom() throws Exception {
StatefulRedisClusterConnection<String, String> statefulConnection = sync.getStatefulConnection();
assertThat(statefulConnection.getReadFrom()).isEqualTo(ReadFrom.MASTER);
statefulConnection.setReadFrom(ReadFrom.NEAREST);
assertThat(statefulConnection.getReadFrom()).isEqualTo(ReadFrom.NEAREST);
}
@Test(expected = IllegalArgumentException.class)
public void testReadFromNull() throws Exception {
sync.getStatefulConnection().setReadFrom(null);
}
@Test
public void testPfmerge() throws Exception {
RedisAdvancedClusterConnection<String, String> connection = clusterClient.connectCluster();
assertThat(SlotHash.getSlot("key2660")).isEqualTo(SlotHash.getSlot("key7112")).isEqualTo(SlotHash.getSlot("key8885"));
connection.pfadd("key2660", "rand", "mat");
connection.pfadd("key7112", "mat", "perrin");
connection.pfmerge("key8885", "key2660", "key7112");
assertThat(connection.pfcount("key8885")).isEqualTo(3);
connection.close();
}
private void assertTimeout(StatefulConnection<?, ?> connection, long expectedTimeout, TimeUnit expectedTimeUnit) {
AssertionsForClassTypes.assertThat(ReflectionTestUtils.getField(connection, "timeout")).isEqualTo(expectedTimeout);
AssertionsForClassTypes.assertThat(ReflectionTestUtils.getField(connection, "unit")).isEqualTo(expectedTimeUnit);
}
}