package com.lambdaworks.redis.cluster; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.Function; import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runners.model.Statement; import com.lambdaworks.redis.api.async.RedisAsyncCommands; import com.lambdaworks.redis.api.sync.RedisCommands; import com.lambdaworks.redis.cluster.api.async.RedisClusterAsyncCommands; import com.lambdaworks.redis.cluster.models.partitions.ClusterPartitionParser; import com.lambdaworks.redis.cluster.models.partitions.Partitions; import com.lambdaworks.redis.cluster.models.partitions.RedisClusterNode; /** * @author Mark Paluch */ public class ClusterRule implements TestRule { private RedisClusterClient clusterClient; private int[] ports; private Map<Integer, RedisAsyncCommands<String, String>> connectionCache = new HashMap<>(); public ClusterRule(RedisClusterClient clusterClient, int... ports) { this.clusterClient = clusterClient; this.ports = ports; for (int port : ports) { RedisAsyncCommands<String, String> connection = clusterClient.connectToNode( new InetSocketAddress("localhost", port)).async(); connectionCache.put(port, connection); } } @Override public Statement apply(final Statement base, Description description) { final Statement beforeCluster = new Statement() { @Override public void evaluate() throws Throwable { flushdb(); } }; return new Statement() { @Override public void evaluate() throws Throwable { beforeCluster.evaluate(); base.evaluate(); } }; } /** * * @return true if the cluster state is {@code ok} and there are no failing nodes */ public boolean isStable() { for (RedisAsyncCommands<String, String> commands : connectionCache.values()) { try { RedisCommands<String, String> sync = commands.getStatefulConnection().sync(); String info = sync.clusterInfo(); if (info != null && info.contains("cluster_state:ok")) { String s = sync.clusterNodes(); Partitions parse = ClusterPartitionParser.parse(s); for (RedisClusterNode redisClusterNode : parse) { if (redisClusterNode.getFlags().contains(RedisClusterNode.NodeFlag.FAIL) || redisClusterNode.getFlags().contains(RedisClusterNode.NodeFlag.EVENTUAL_FAIL) || redisClusterNode.getFlags().contains(RedisClusterNode.NodeFlag.HANDSHAKE)) { return false; } } } else { return false; } } catch (Exception e) { // nothing to do } } return true; } /** * Flush data on all nodes, ignore failures. */ public void flushdb() { onAllConnections(c -> c.flushdb(), true); } /** * Cluster reset on all nodes. */ public void clusterReset() { onAllConnections(c -> c.clusterReset(true)); onAllConnections(RedisClusterAsyncCommands::clusterFlushslots); } /** * Meet on all nodes. * * @param host * @param port */ public void meet(String host, int port) { onAllConnections(c -> c.clusterMeet(host, port)); } public RedisClusterClient getClusterClient() { return clusterClient; } @SuppressWarnings("rawtypes") private <T> void onAllConnections(Function<RedisClusterAsyncCommands<?, ?>, Future<T>> function) { onAllConnections(function, false); } @SuppressWarnings("rawtypes") private <T> void onAllConnections(Function<RedisClusterAsyncCommands<?, ?>, Future<T>> function, boolean ignoreExecutionException) { List<Future<?>> futures = new ArrayList<>(); for (RedisClusterAsyncCommands<?, ?> connection : connectionCache.values()) { futures.add(function.apply(connection)); } try { await((List) futures, ignoreExecutionException); } catch (InterruptedException | ExecutionException | TimeoutException e) { throw new IllegalStateException(e); } } private void await(List<Future<?>> futures, boolean ignoreExecutionException) throws InterruptedException, java.util.concurrent.ExecutionException, java.util.concurrent.TimeoutException { for (Future<?> future : futures) { try { future.get(10, TimeUnit.SECONDS); } catch (ExecutionException e) { if (!ignoreExecutionException) { throw e; } } } } }