package com.lambdaworks.redis.masterslave;
import static com.lambdaworks.redis.masterslave.MasterSlaveUtils.CODEC;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import com.lambdaworks.redis.RedisClient;
import com.lambdaworks.redis.RedisException;
import com.lambdaworks.redis.RedisFuture;
import com.lambdaworks.redis.RedisURI;
import com.lambdaworks.redis.internal.LettuceAssert;
import com.lambdaworks.redis.models.role.RedisInstance;
import com.lambdaworks.redis.models.role.RedisNodeDescription;
import com.lambdaworks.redis.sentinel.api.StatefulRedisSentinelConnection;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
/**
* Topology provider using Redis Sentinel and the Sentinel API.
*
* @author Mark Paluch
* @since 4.1
*/
public class SentinelTopologyProvider implements TopologyProvider {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(SentinelTopologyProvider.class);
private final String masterId;
private final RedisClient redisClient;
private final RedisURI sentinelUri;
private final long timeout;
private final TimeUnit timeUnit;
/**
* Creates a new {@link SentinelTopologyProvider}.
*
* @param masterId must not be empty
* @param redisClient must not be {@literal null}.
* @param sentinelUri must not be {@literal null}.
*/
public SentinelTopologyProvider(String masterId, RedisClient redisClient, RedisURI sentinelUri) {
LettuceAssert.notEmpty(masterId, "MasterId must not be empty");
LettuceAssert.notNull(redisClient, "RedisClient must not be null");
LettuceAssert.notNull(sentinelUri, "Sentinel URI must not be null");
this.masterId = masterId;
this.redisClient = redisClient;
this.sentinelUri = sentinelUri;
this.timeout = sentinelUri.getTimeout();
this.timeUnit = sentinelUri.getUnit();
}
@Override
public List<RedisNodeDescription> getNodes() {
logger.debug("lookup topology for masterId {}", masterId);
try (StatefulRedisSentinelConnection<String, String> connection = redisClient.connectSentinel(CODEC, sentinelUri)) {
RedisFuture<Map<String, String>> masterFuture = connection.async().master(masterId);
RedisFuture<List<Map<String, String>>> slavesFuture = connection.async().slaves(masterId);
List<RedisNodeDescription> result = new ArrayList<>();
try {
Map<String, String> master = masterFuture.get(timeout, timeUnit);
List<Map<String, String>> slaves = slavesFuture.get(timeout, timeUnit);
result.add(toNode(master, RedisInstance.Role.MASTER));
result.addAll(slaves.stream().filter(SentinelTopologyProvider::isAvailable)
.map(map -> toNode(map, RedisInstance.Role.SLAVE)).collect(Collectors.toList()));
} catch (ExecutionException | InterruptedException | TimeoutException e) {
throw new RedisException(e);
}
return result;
}
}
private static boolean isAvailable(Map<String, String> map) {
String flags = map.get("flags");
if (flags != null) {
if (flags.contains("s_down") || flags.contains("o_down") || flags.contains("disconnected")) {
return false;
}
}
return true;
}
private RedisNodeDescription toNode(Map<String, String> map, RedisInstance.Role role) {
String ip = map.get("ip");
String port = map.get("port");
return new RedisMasterSlaveNode(ip, Integer.parseInt(port), sentinelUri, role);
}
}