DATAREDIS-330 - Add support for SENTINEL commands.

add support for commands:
  SENTINEL FAILOVER master
  SENTINEL SLAVES master
  SENTINEL REMOVE name
  SENTINEL MONITOR name ip port quorum

Original pull request: #92.
This commit is contained in:
Christoph Strobl
2014-07-21 21:09:10 +02:00
committed by Thomas Darimont
parent 55ff415a71
commit fd361a1868
29 changed files with 2071 additions and 48 deletions

View File

@@ -222,6 +222,8 @@ public RedisConnectionFactory jedisConnectionFactory() {
return new JedisConnectionFactory(sentinelConfig);
}
]]></programlisting>
<para>Sometimes direct interaction with the one of the Sentinels is required. Using <classname>RedisConnectionFactory.getSentinelConnection()</classname> or
<classname>RedisConnection.getSentinelCommands()</classname> gives you access to the first active Sentinel configured.</para>
</section>
<section id="redis:template">

View File

@@ -0,0 +1,115 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.redis.connection;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.dao.InvalidDataAccessResourceUsageException;
import org.springframework.data.redis.RedisSystemException;
/**
* @author Christoph Strobl
* @since 1.4
*/
public abstract class AbstractRedisConnection implements RedisConnection {
private RedisSentinelConfiguration sentinelConfiguration;
private ConcurrentHashMap<RedisNode, RedisSentinelConnection> connectionCache = new ConcurrentHashMap<RedisNode, RedisSentinelConnection>();
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisConnection#getSentinelCommands()
*/
@Override
public RedisSentinelConnection getSentinelConnection() {
if (!hasRedisSentinelConfigured()) {
throw new InvalidDataAccessResourceUsageException("No sentinels configured.");
}
RedisNode node = selectActiveSentinel();
RedisSentinelConnection connection = connectionCache.get(node);
if (connection == null || !connection.isOpen()) {
connection = getSentinelConnection(node);
connectionCache.putIfAbsent(node, connection);
}
return connection;
}
public void setSentinelConfiguration(RedisSentinelConfiguration sentinelConfiguration) {
this.sentinelConfiguration = sentinelConfiguration;
}
public boolean hasRedisSentinelConfigured() {
return this.sentinelConfiguration != null;
}
private RedisNode selectActiveSentinel() {
for (RedisNode node : this.sentinelConfiguration.getSentinels()) {
if (isActive(node)) {
return node;
}
}
throw new InvalidDataAccessApiUsageException("Could not find any active sentinels");
}
/**
* Check if node is active by sending ping.
*
* @param node
* @return
*/
protected boolean isActive(RedisNode node) {
return false;
}
/**
* Get {@link RedisSentinelCommands} connected to given node.
*
* @param sentinel
* @return
*/
protected RedisSentinelConnection getSentinelConnection(RedisNode sentinel) {
throw new UnsupportedOperationException("Sentinel is not supported by this client.");
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisConnection#close()
*/
@Override
public void close() throws DataAccessException {
if (!connectionCache.isEmpty()) {
for (RedisNode node : connectionCache.keySet()) {
RedisSentinelConnection connection = connectionCache.remove(node);
if (connection.isOpen()) {
try {
connection.close();
} catch (IOException e) {
throw new RedisSystemException("Failed to close sentinel connection", e);
}
}
}
}
}
}

View File

@@ -2380,4 +2380,9 @@ public class DefaultStringRedisConnection implements StringRedisConnection {
new TupleConverter());
}
@Override
public RedisSentinelConnection getSentinelConnection() {
return delegate.getSentinelConnection();
}
}

View File

@@ -92,4 +92,10 @@ public interface RedisConnection extends RedisCommands {
* @return the result of the executed commands.
*/
List<Object> closePipeline() throws RedisPipelineException;
/**
* @return
* @since 1.4
*/
RedisSentinelConnection getSentinelConnection();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2013 the original author or authors.
* Copyright 2011-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@ import org.springframework.dao.support.PersistenceExceptionTranslator;
* Thread-safe factory of Redis connections.
*
* @author Costin Leau
* @author Christoph Strobl
*/
public interface RedisConnectionFactory extends PersistenceExceptionTranslator {
@@ -41,4 +42,10 @@ public interface RedisConnectionFactory extends PersistenceExceptionTranslator {
* @return Whether or not to convert pipeline and tx results
*/
boolean getConvertPipelineAndTxResults();
/**
* @return
* @since 1.4
*/
RedisSentinelConnection getSentinelConnection();
}

View File

@@ -15,22 +15,37 @@
*/
package org.springframework.data.redis.connection;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* @author Christoph Strobl
* @author Thomas Darimont
*
* @since 1.4
*/
public class RedisNode {
public class RedisNode implements NamedNode {
private final String host;
private final int port;
private String name;
private String host;
private int port;
/**
* Creates a new {@link RedisNode} with the given {@code host}, {@code port}.
*
* @param host must not be {@literal null}
* @param port
*/
public RedisNode(String host, int port) {
Assert.notNull(host,"host must not be null!");
this.host = host;
this.port = port;
}
protected RedisNode() {}
public String getHost() {
return host;
}
@@ -43,6 +58,15 @@ public class RedisNode {
return host + ":" + port;
}
@Override
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return asString();
@@ -77,7 +101,42 @@ public class RedisNode {
return false;
}
if (!ObjectUtils.nullSafeEquals(this.name, other.name)) {
return false;
}
return true;
}
/**
* @author Christoph Strobl
*
* @since 1.4
*/
public static class RedisNodeBuilder {
private RedisNode node;
public RedisNodeBuilder() {
node = new RedisNode();
}
public RedisNodeBuilder withName(String name) {
node.name = name;
return this;
}
public RedisNodeBuilder listeningAt(String host, int port) {
Assert.hasText(host, "Hostname must not be empty or null.");
node.host = host;
node.port = port;
return this;
}
public RedisNode build() {
return this.node;
}
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.redis.connection;
import java.util.Collection;
/**
* @author Christoph Strobl
* @since 1.4
*/
public interface RedisSentinelCommands {
/**
* Force a failover as if the {@literal master} was not reachable.
*
* @param master must not be {@literal null}.
*/
void failover(NamedNode master);
/**
* Get a {@link Collection} of monitored masters and their state.
*
* @return Collection of {@link RedisServer}s. Never {@literal null}.
*/
Collection<RedisServer> masters();
/**
* Show list of slaves for given {@literal master}.
*
* @param master must not be {@literal null}.
* @return Collection of {@link RedisServer}s. Never {@literal null}.
*/
Collection<RedisServer> slaves(NamedNode master);
/**
* Removes given {@literal master}. The server will no longer be monitored and will no longer be returned by
* {@link #masters()}.
*
* @param master must not be {@literal null}.
*/
void remove(NamedNode master);
/**
* Tell sentinel to start monitoring a new {@literal master} with the specified {@link RedisServer#getName()},
* {@link RedisServer#getHost()}, {@link RedisServer#getPort()}, and {@link RedisServer#getQuorum()}.
*
* @param master must not be {@literal null}.
*/
void monitor(RedisServer master);
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.redis.connection;
import java.io.Closeable;
/**
* @author Christoph Strobl
* @since 1.4
*/
public interface RedisSentinelConnection extends RedisSentinelCommands, Closeable {
/**
* @return true if connected to server
*/
boolean isOpen();
}

View File

@@ -0,0 +1,213 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.redis.connection;
import java.util.Properties;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* @author Christoph Strobl
* @author Thomas Darimont
*
* @since 1.4
*/
public class RedisServer extends RedisNode {
public static enum INFO {
NAME("name"), //
HOST("ip"), //
PORT("port"), //
RUN_ID("runid"), //
FLAGS("flags"), //
PENDING_COMMANDS("pending-commands"), //
LAST_PING_SENT("last-ping-sent"), //
LAST_OK_PING_REPLY("last-ok-ping-reply"), //
DOWN_AFTER_MILLISECONDS("down-after-milliseconds"), //
INFO_REFRESH("info-refresh"), //
ROLE_REPORTED("role-reported"), //
ROLE_REPORTED_TIME("role-reported-time"), //
CONFIG_EPOCH("config-epoch"), //
NUMBER_SLAVES("num-slaves"), //
NUMBER_OTHER_SENTINELS("multi"), //
BUFFER_LENGTH("qbuf"), //
BUFFER_FREE_SPACE("qbuf-free"), //
OUTPUT_BUFFER_LENGTH("obl"), //
OUTPUT_LIST_LENGTH("number-other-sentinels"), //
QUORUM("quorum"), //
FAILOVER_TIMEOUT("failover-timeout"), //
PARALLEL_SYNCS("parallel-syncs"); //
String key;
INFO(String key) {
this.key = key;
}
}
private Properties properties;
/**
* Creates a new {@link RedisServer} with the given {@code host}, {@code port}.
*
* @param host must not be {@literal null}
* @param port
*/
public RedisServer(String host, int port) {
this(host, port, new Properties());
}
/**
* Creates a new {@link RedisServer} with the given {@code host}, {@code port} and {@code properties}.
*
* @param host must not be {@literal null}
* @param port
* @param properties may be {@literal null}
*/
public RedisServer(String host, int port, Properties properties) {
super(host, port);
this.properties = properties;
String name = host + ":" + port;
if (properties != null && properties.containsKey(INFO.NAME.key)) {
name = get(INFO.NAME);
}
setName(name);
}
/**
* Creates a new {@link RedisServer} from the given properties.
*
* @param properties
* @return
*/
public static RedisServer newServerFrom(Properties properties) {
String host = properties.getProperty(INFO.HOST.key, "127.0.0.1");
int port = Integer.parseInt(properties.getProperty(INFO.PORT.key, "26379"));
return new RedisServer(host, port, properties);
}
public void setQuorum(Long quorum) {
if (quorum == null) {
this.properties.remove(INFO.QUORUM.key);
return;
}
this.properties.put(INFO.QUORUM.key, quorum.toString());
}
public String getRunId() {
return get(INFO.RUN_ID);
}
public String getFlags() {
return get(INFO.FLAGS);
}
public boolean isMaster() {
String role = getRoleReported();
if (!StringUtils.hasText(role)) {
return false;
}
return role.equalsIgnoreCase("master");
}
public Long getPendingCommands() {
return getLongValueOf(INFO.PENDING_COMMANDS);
}
public Long getLastPingSent() {
return getLongValueOf(INFO.LAST_PING_SENT);
}
public Long getLastOkPingReply() {
return getLongValueOf(INFO.LAST_OK_PING_REPLY);
}
public Long getDownAfterMilliseconds() {
return getLongValueOf(INFO.DOWN_AFTER_MILLISECONDS);
}
public Long getInfoRefresh() {
return getLongValueOf(INFO.INFO_REFRESH);
}
public String getRoleReported() {
return get(INFO.ROLE_REPORTED);
}
public Long roleReportedTime() {
return getLongValueOf(INFO.ROLE_REPORTED_TIME);
}
public Long getConfigEpoch() {
return getLongValueOf(INFO.CONFIG_EPOCH);
}
public Long getNumberSlaves() {
return getLongValueOf(INFO.NUMBER_SLAVES);
}
public Long getNumberOtherSentinels() {
return getLongValueOf(INFO.NUMBER_OTHER_SENTINELS);
}
public Long getQuorum() {
return getLongValueOf(INFO.QUORUM);
}
public Long getFailoverTimeout() {
return getLongValueOf(INFO.FAILOVER_TIMEOUT);
}
public Long getParallelSyncs() {
return getLongValueOf(INFO.PARALLEL_SYNCS);
}
/**
* @param info must not be null
* @return {@literal null} if no entry found for requested {@link INFO}.
*/
public String get(INFO info) {
Assert.notNull(info, "Cannot retrieve client information for 'null'.");
return get(info.key);
}
/**
* @param key must not be {@literal null} or {@literal empty}.
* @return {@literal null} if no entry found for requested {@code key}.
*/
public String get(String key) {
Assert.hasText(key, "Cannot get information for 'empty' / 'null' key.");
return this.properties.getProperty(key);
}
private Long getLongValueOf(INFO info) {
String value = get(info);
return value == null ? null : Long.valueOf(value);
}
}

View File

@@ -17,6 +17,7 @@ package org.springframework.data.redis.connection.convert;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
@@ -38,6 +39,7 @@ abstract public class Converters {
private static final Converter<String, Properties> STRING_TO_PROPS = new StringToPropertiesConverter();
private static final Converter<Long, Boolean> LONG_TO_BOOLEAN = new LongToBooleanConverter();
private static final Converter<String, DataType> STRING_TO_DATA_TYPE = new StringToDataTypeConverter();
private static final Converter<Map<?, ?>, Properties> MAP_TO_PROPERTIES = MapToPropertiesConverter.INSTANCE;
public static Converter<String, Properties> stringToProps() {
return STRING_TO_PROPS;
@@ -55,6 +57,10 @@ abstract public class Converters {
return STRING_TO_PROPS.convert(source);
}
public static Properties toProperties(Map<?, ?> source) {
return MAP_TO_PROPERTIES.convert(source);
}
public static Boolean toBoolean(Long source) {
return LONG_TO_BOOLEAN.convert(source);
}
@@ -76,7 +82,6 @@ abstract public class Converters {
return tupleArgs;
}
/**
* Returns the timestamp constructed from the given {@code seconds} and {@code microseconds}.
*

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.redis.connection.convert;
import java.util.Map;
import java.util.Properties;
import org.springframework.core.convert.converter.Converter;
/**
* @author Christoph Strobl
* @since 1.4
*/
public enum MapToPropertiesConverter implements Converter<Map<?, ?>, Properties> {
INSTANCE;
@Override
public Properties convert(Map<?, ?> source) {
Properties p = new Properties();
if (source != null) {
p.putAll(source);
}
return p;
}
}

View File

@@ -34,10 +34,11 @@ import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.ExceptionTranslationStrategy;
import org.springframework.data.redis.FallbackExceptionTranslationStrategy;
import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.data.redis.connection.AbstractRedisConnection;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.connection.FutureResult;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.RedisPipelineException;
import org.springframework.data.redis.connection.RedisSubscribedConnectionException;
import org.springframework.data.redis.connection.RedisZSetCommands;
@@ -85,7 +86,7 @@ import redis.clients.util.Pool;
* @author Thomas Darimont
* @author Jungtaek Lim
*/
public class JedisConnection implements RedisConnection {
public class JedisConnection extends AbstractRedisConnection {
private static final Field CLIENT_FIELD;
private static final Method SEND_COMMAND;
@@ -234,6 +235,7 @@ public class JedisConnection implements RedisConnection {
}
public void close() throws DataAccessException {
super.close();
// return the connection to the pool
if (pool != null) {
if (!broken) {
@@ -3114,4 +3116,35 @@ public class JedisConnection implements RedisConnection {
return args;
}
@Override
protected boolean isActive(RedisNode node) {
if (node == null) {
return false;
}
Jedis temp = null;
try {
temp = getJedis(node);
temp.connect();
return temp.ping().equalsIgnoreCase("pong");
} catch (Exception e) {
return false;
} finally {
if (temp != null) {
temp.disconnect();
temp.close();
}
}
}
@Override
protected JedisSentinelConnection getSentinelConnection(RedisNode sentinel) {
return new JedisSentinelConnection(getJedis(sentinel));
}
protected Jedis getJedis(RedisNode node) {
return new Jedis(node.getHost(), node.getPort());
}
}

View File

@@ -26,6 +26,7 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.InvalidDataAccessResourceUsageException;
import org.springframework.data.redis.ExceptionTranslationStrategy;
import org.springframework.data.redis.PassThroughExceptionTranslationStrategy;
import org.springframework.data.redis.RedisConnectionFailureException;
@@ -33,6 +34,7 @@ import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.RedisSentinelConnection;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
@@ -413,6 +415,32 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean,
return sentinelConfig != null;
}
/* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisConnectionFactory#getSentinelConnection()
*/
@Override
public RedisSentinelConnection getSentinelConnection() {
if (!isRedisSentinelAware()) {
throw new InvalidDataAccessResourceUsageException("No Sentinels configured");
}
return new JedisSentinelConnection(getActiveSentinel());
}
private Jedis getActiveSentinel() {
Assert.notNull(this.sentinelConfig);
for (RedisNode node : this.sentinelConfig.getSentinels()) {
Jedis jedis = new Jedis(node.getHost(), node.getPort());
if (jedis.ping().equalsIgnoreCase("pong")) {
return jedis;
}
}
throw new InvalidDataAccessResourceUsageException("no sentinel found");
}
private Set<String> convertToJedisSentinelSet(Collection<RedisNode> nodes) {
if (CollectionUtils.isEmpty(nodes)) {

View File

@@ -15,15 +15,18 @@
*/
package org.springframework.data.redis.connection.jedis;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.springframework.core.convert.converter.Converter;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.DefaultTuple;
import org.springframework.data.redis.connection.RedisListCommands.Position;
import org.springframework.data.redis.connection.RedisServer;
import org.springframework.data.redis.connection.RedisStringCommands.BitOperation;
import org.springframework.data.redis.connection.RedisZSetCommands.Tuple;
import org.springframework.data.redis.connection.SortParameters;
@@ -36,6 +39,7 @@ import org.springframework.data.redis.connection.convert.SetConverter;
import org.springframework.data.redis.connection.convert.StringToRedisClientInfoConverter;
import org.springframework.data.redis.core.types.RedisClientInfo;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import redis.clients.jedis.BinaryClient.LIST_POSITION;
@@ -61,6 +65,7 @@ abstract public class JedisConverters extends Converters {
private static final Converter<Exception, DataAccessException> EXCEPTION_CONVERTER = new JedisExceptionConverter();
private static final Converter<String[], List<RedisClientInfo>> STRING_TO_CLIENT_INFO_CONVERTER = new StringToRedisClientInfoConverter();
private static final Converter<redis.clients.jedis.Tuple, Tuple> TUPLE_CONVERTER;
private static final Converter<Properties, RedisServer> PROPERTIES_TO_SENTINEL;
private static final ListConverter<redis.clients.jedis.Tuple, Tuple> TUPLE_LIST_TO_TUPLE_LIST_CONVERTER;
static {
@@ -80,6 +85,14 @@ abstract public class JedisConverters extends Converters {
};
TUPLE_SET_TO_TUPLE_SET = new SetConverter<redis.clients.jedis.Tuple, Tuple>(TUPLE_CONVERTER);
TUPLE_LIST_TO_TUPLE_LIST_CONVERTER = new ListConverter<redis.clients.jedis.Tuple, Tuple>(TUPLE_CONVERTER);
PROPERTIES_TO_SENTINEL = new Converter<Properties, RedisServer>() {
@Override
public RedisServer convert(Properties source) {
return source != null ? RedisServer.newServerFrom(source) : null;
}
};
}
public static Converter<String, byte[]> stringToBytes() {
@@ -157,6 +170,28 @@ abstract public class JedisConverters extends Converters {
return STRING_TO_CLIENT_INFO_CONVERTER.convert(source.split("\\r?\\n"));
}
/**
* @param source
* @return
* @since 1.4
*/
public static List<RedisServer> toListOfRedisServer(List<Map<String, String>> source) {
if (CollectionUtils.isEmpty(source)) {
return Collections.emptyList();
}
List<RedisServer> sentinels = new ArrayList<RedisServer>();
for (Map<String, String> info : source) {
sentinels.add(RedisServer.newServerFrom(Converters.toProperties(info)));
}
return sentinels;
}
public static DataAccessException toDataAccessException(Exception ex) {
return EXCEPTION_CONVERTER.convert(ex);
}
public static LIST_POSITION toListPosition(Position source) {
Assert.notNull("list positions are mandatory");
return (Position.AFTER.equals(source) ? LIST_POSITION.AFTER : LIST_POSITION.BEFORE);

View File

@@ -0,0 +1,162 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.redis.connection.jedis;
import java.io.IOException;
import java.util.List;
import org.springframework.data.redis.connection.NamedNode;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.RedisSentinelCommands;
import org.springframework.data.redis.connection.RedisSentinelConnection;
import org.springframework.data.redis.connection.RedisServer;
import org.springframework.util.Assert;
import redis.clients.jedis.Jedis;
/**
* @author Christoph Strobl
* @since 1.4
*/
public class JedisSentinelConnection implements RedisSentinelConnection {
private Jedis jedis;
public JedisSentinelConnection(RedisNode sentinel) {
this(sentinel.getHost(), sentinel.getPort());
}
public JedisSentinelConnection(String host, int port) {
this(new Jedis(host, port));
}
public JedisSentinelConnection(Jedis jedis) {
Assert.notNull(jedis, "Cannot created JedisSentinelConnection using 'null' as client.");
this.jedis = jedis;
init();
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisSentinelCommands#failover(org.springframework.data.redis.connection.NamedNode)
*/
@Override
public void failover(NamedNode master) {
Assert.notNull(master, "Redis node master must not be 'null' for failover.");
Assert.hasText(master.getName(), "Redis master name must not be 'null' or empty for failover.");
jedis.sentinelFailover(master.getName());
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisSentinelCommands#masters()
*/
@Override
public List<RedisServer> masters() {
return JedisConverters.toListOfRedisServer(jedis.sentinelMasters());
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisSentinelCommands#slaves(org.springframework.data.redis.connection.NamedNode)
*/
@Override
public List<RedisServer> slaves(NamedNode master) {
Assert.notNull(master, "Master node cannot be 'null' when loading slaves.");
return slaves(master.getName());
}
/**
* @param masterName
* @see RedisSentinelCommands#slaves(NamedNode)
* @return
*/
public List<RedisServer> slaves(String masterName) {
Assert.hasText(masterName, "Name of redis master cannot be 'null' or empty when loading slaves.");
return JedisConverters.toListOfRedisServer(jedis.sentinelSlaves(masterName));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisSentinelCommands#remove(org.springframework.data.redis.connection.NamedNode)
*/
@Override
public void remove(NamedNode master) {
Assert.notNull(master, "Master node cannot be 'null' when trying to remove.");
remove(master.getName());
}
/**
* @param masterName
* @see RedisSentinelCommands#remove(NamedNode)
*/
public void remove(String masterName) {
Assert.hasText(masterName, "Name of redis master cannot be 'null' or empty when trying to remove.");
jedis.sentinelRemove(masterName);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisSentinelCommands#monitor(org.springframework.data.redis.connection.RedisServer)
*/
@Override
public void monitor(RedisServer server) {
Assert.notNull(server, "Cannot monitor 'null' server.");
Assert.hasText(server.getName(), "Name of server to monitor must not be 'null' or empty.");
Assert.hasText(server.getHost(), "Host must not be 'null' for server to monitor.");
Assert.notNull(server.getPort(), "Port must not be 'null' for server to monitor.");
Assert.notNull(server.getQuorum(), "Quorum must not be 'null' for server to monitor.");
jedis.sentinelMonitor(server.getName(), server.getHost(), server.getPort().intValue(), server.getQuorum()
.intValue());
}
/*
* (non-Javadoc)
* @see java.io.Closeable#close()
*/
@Override
public void close() throws IOException {
jedis.close();
}
private void init() {
if (!jedis.isConnected()) {
doInit(jedis);
}
}
/**
* Do what ever is required to establish the connection to redis.
*
* @param jedis
*/
protected void doInit(Jedis jedis) {
jedis.connect();
}
@Override
public boolean isOpen() {
return jedis != null && jedis.isConnected();
}
}

View File

@@ -37,10 +37,10 @@ import org.jredis.protocol.Command;
import org.jredis.ri.alphazero.JRedisSupport;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.RedisSystemException;
import org.springframework.data.redis.connection.AbstractRedisConnection;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.connection.Pool;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.connection.SortParameters;
import org.springframework.data.redis.connection.Subscription;
@@ -59,7 +59,7 @@ import org.springframework.util.ReflectionUtils;
* @author Christoph Strobl
* @author Thomas Darimont
*/
public class JredisConnection implements RedisConnection {
public class JredisConnection extends AbstractRedisConnection {
private static final Method SERVICE_REQUEST;
@@ -120,6 +120,8 @@ public class JredisConnection implements RedisConnection {
}
public void close() throws RedisSystemException {
super.close();
if (isClosed()) {
return;
}
@@ -1275,4 +1277,5 @@ public class JredisConnection implements RedisConnection {
public Cursor<Entry<byte[], byte[]>> hScan(byte[] key, ScanOptions options) {
throw new UnsupportedOperationException("'HSCAN' command is not uspported for jredis");
}
}

View File

@@ -28,6 +28,7 @@ import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.Pool;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisSentinelConnection;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -201,4 +202,9 @@ public class JredisConnectionFactory implements InitializingBean, DisposableBean
public boolean getConvertPipelineAndTxResults() {
return false;
}
@Override
public RedisSentinelConnection getSentinelConnection() {
throw new UnsupportedOperationException();
}
}

View File

@@ -42,12 +42,12 @@ import org.springframework.dao.DataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.dao.QueryTimeoutException;
import org.springframework.data.redis.ExceptionTranslationStrategy;
import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.data.redis.FallbackExceptionTranslationStrategy;
import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.data.redis.connection.AbstractRedisConnection;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.connection.FutureResult;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisPipelineException;
import org.springframework.data.redis.connection.RedisSubscribedConnectionException;
import org.springframework.data.redis.connection.ReturnType;
@@ -100,7 +100,7 @@ import com.lambdaworks.redis.pubsub.RedisPubSubConnection;
* @author Christoph Strobl
* @author Thomas Darimont
*/
public class LettuceConnection implements RedisConnection {
public class LettuceConnection extends AbstractRedisConnection {
private static final ExceptionTranslationStrategy EXCEPTION_TRANSLATION = new FallbackExceptionTranslationStrategy(
LettuceConverters.exceptionConverter());
@@ -358,6 +358,8 @@ public class LettuceConnection implements RedisConnection {
}
public void close() throws DataAccessException {
super.close();
isClosed = true;
if (asyncDedicatedConn != null) {

View File

@@ -29,6 +29,7 @@ import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.data.redis.connection.Pool;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisSentinelConnection;
import org.springframework.util.Assert;
import com.lambdaworks.redis.RedisAsyncConnection;
@@ -338,4 +339,9 @@ public class LettuceConnectionFactory implements InitializingBean, DisposableBea
client.setDefaultTimeout(timeout, TimeUnit.MILLISECONDS);
return client;
}
@Override
public RedisSentinelConnection getSentinelConnection() {
throw new UnsupportedOperationException();
}
}

View File

@@ -32,12 +32,12 @@ import java.util.concurrent.Future;
import org.springframework.core.convert.converter.Converter;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.ExceptionTranslationStrategy;
import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.data.redis.FallbackExceptionTranslationStrategy;
import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.data.redis.connection.AbstractRedisConnection;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.connection.FutureResult;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisPipelineException;
import org.springframework.data.redis.connection.RedisSubscribedConnectionException;
import org.springframework.data.redis.connection.ReturnType;
@@ -69,7 +69,7 @@ import com.google.common.util.concurrent.ListenableFuture;
* @author Christoph Strobl
* @author Thomas Darimont
*/
public class SrpConnection implements RedisConnection {
public class SrpConnection extends AbstractRedisConnection {
private static final ExceptionTranslationStrategy EXCEPTION_TRANSLATION = new FallbackExceptionTranslationStrategy(
SrpConverters.exceptionConverter());
@@ -265,6 +265,8 @@ public class SrpConnection implements RedisConnection {
}
public void close() throws DataAccessException {
super.close();
isClosed = true;
queue.remove(this);

View File

@@ -26,6 +26,7 @@ import org.springframework.data.redis.ExceptionTranslationStrategy;
import org.springframework.data.redis.PassThroughExceptionTranslationStrategy;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisSentinelConnection;
/**
* Connection factory creating <a href="http://github.com/spullara/redis-protocol">Redis Protocol</a> based connections.
@@ -159,4 +160,9 @@ public class SrpConnectionFactory implements InitializingBean, DisposableBean, R
public void setConvertPipelineAndTxResults(boolean convertPipelineAndTxResults) {
this.convertPipelineAndTxResults = convertPipelineAndTxResults;
}
@Override
public RedisSentinelConnection getSentinelConnection() {
throw new UnsupportedOperationException();
}
}

View File

@@ -0,0 +1,788 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.redis.connection;
import static org.mockito.Mockito.*;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisNode.RedisNodeBuilder;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.types.RedisClientInfo;
import org.springframework.util.ObjectUtils;
/**
* @author Christoph Strobl
*/
public class RedisConnectionUnitTests {
private final RedisNode SENTINEL_1 = new RedisNodeBuilder().listeningAt("localhost", 23679).build();
private AbstractDelegatingRedisConnectionStub connection;
private RedisSentinelConnection sentinelConnectionMock;
@Before
public void setUp() {
sentinelConnectionMock = mock(RedisSentinelConnection.class);
connection = new AbstractDelegatingRedisConnectionStub(mock(AbstractRedisConnection.class, CALLS_REAL_METHODS));
connection.setSentinelConfiguration(new RedisSentinelConfiguration().master("mymaster").sentinel(SENTINEL_1));
connection.setSentinelConnection(sentinelConnectionMock);
}
/**
* @see DATAREDIS-330
*/
@Test
public void shouldCloseSentinelConnectionAlongWithRedisConnection() throws IOException {
when(sentinelConnectionMock.isOpen()).thenReturn(true).thenReturn(false);
connection.setActiveNode(SENTINEL_1);
connection.getSentinelConnection();
connection.close();
verify(sentinelConnectionMock, times(1)).close();
}
/**
* @see DATAREDIS-330
*/
@Test
public void shouldNotTryToCloseSentinelConnectionsWhenAlreadyClosed() throws IOException {
when(sentinelConnectionMock.isOpen()).thenReturn(true);
when(sentinelConnectionMock.isOpen()).thenReturn(false);
connection.setActiveNode(SENTINEL_1);
connection.getSentinelConnection();
connection.close();
verify(sentinelConnectionMock, never()).close();
}
static class AbstractDelegatingRedisConnectionStub extends AbstractRedisConnection {
RedisConnection delegate;
RedisNode activeNode;
RedisSentinelConnection sentinelConnection;
public AbstractDelegatingRedisConnectionStub(RedisConnection delegate) {
this.delegate = delegate;
}
@Override
protected boolean isActive(RedisNode node) {
return ObjectUtils.nullSafeEquals(activeNode, node);
}
public void setActiveNode(RedisNode activeNode) {
this.activeNode = activeNode;
}
public void setSentinelConnection(RedisSentinelConnection sentinelConnection) {
this.sentinelConnection = sentinelConnection;
}
public boolean isSubscribed() {
return delegate.isSubscribed();
}
public void scriptFlush() {
delegate.scriptFlush();
}
public void select(int dbIndex) {
delegate.select(dbIndex);
}
public void multi() {
delegate.multi();
}
public Long rPush(byte[] key, byte[]... values) {
return delegate.rPush(key, values);
}
public byte[] get(byte[] key) {
return delegate.get(key);
}
public void scriptKill() {
delegate.scriptKill();
}
public Long sAdd(byte[] key, byte[]... values) {
return delegate.sAdd(key, values);
}
public Boolean exists(byte[] key) {
return delegate.exists(key);
}
public Subscription getSubscription() {
return delegate.getSubscription();
}
public byte[] echo(byte[] message) {
return delegate.echo(message);
}
public Boolean hSet(byte[] key, byte[] field, byte[] value) {
return delegate.hSet(key, field, value);
}
public void bgWriteAof() {
delegate.bgWriteAof();
}
public Object execute(String command, byte[]... args) {
return delegate.execute(command, args);
}
public String scriptLoad(byte[] script) {
return delegate.scriptLoad(script);
}
public byte[] getSet(byte[] key, byte[] value) {
return delegate.getSet(key, value);
}
public List<Object> exec() {
return delegate.exec();
}
public Long lPush(byte[] key, byte[]... value) {
return delegate.lPush(key, value);
}
public Long del(byte[]... keys) {
return delegate.del(keys);
}
public void close() throws DataAccessException {
super.close();
}
public String ping() {
return delegate.ping();
}
public Long sRem(byte[] key, byte[]... values) {
return delegate.sRem(key, values);
}
public Boolean zAdd(byte[] key, double score, byte[] value) {
return delegate.zAdd(key, score, value);
}
public Long publish(byte[] channel, byte[] message) {
return delegate.publish(channel, message);
}
public Boolean hSetNX(byte[] key, byte[] field, byte[] value) {
return delegate.hSetNX(key, field, value);
}
public void bgReWriteAof() {
delegate.bgReWriteAof();
}
public List<byte[]> mGet(byte[]... keys) {
return delegate.mGet(keys);
}
public boolean isClosed() {
return delegate.isClosed();
}
public Long rPushX(byte[] key, byte[] value) {
return delegate.rPushX(key, value);
}
public DataType type(byte[] key) {
return delegate.type(key);
}
public List<Boolean> scriptExists(String... scriptShas) {
return delegate.scriptExists(scriptShas);
}
public byte[] sPop(byte[] key) {
return delegate.sPop(key);
}
public void bgSave() {
delegate.bgSave();
}
public void set(byte[] key, byte[] value) {
delegate.set(key, value);
}
public void discard() {
delegate.discard();
}
public Object getNativeConnection() {
return delegate.getNativeConnection();
}
public Long zAdd(byte[] key, Set<Tuple> tuples) {
return delegate.zAdd(key, tuples);
}
public void subscribe(MessageListener listener, byte[]... channels) {
delegate.subscribe(listener, channels);
}
public Set<byte[]> keys(byte[] pattern) {
return delegate.keys(pattern);
}
public byte[] hGet(byte[] key, byte[] field) {
return delegate.hGet(key, field);
}
public Long lPushX(byte[] key, byte[] value) {
return delegate.lPushX(key, value);
}
public Long lastSave() {
return delegate.lastSave();
}
public void watch(byte[]... keys) {
delegate.watch(keys);
}
public Boolean sMove(byte[] srcKey, byte[] destKey, byte[] value) {
return delegate.sMove(srcKey, destKey, value);
}
public Boolean setNX(byte[] key, byte[] value) {
return delegate.setNX(key, value);
}
public <T> T eval(byte[] script, ReturnType returnType, int numKeys, byte[]... keysAndArgs) {
return delegate.eval(script, returnType, numKeys, keysAndArgs);
}
public boolean isQueueing() {
return delegate.isQueueing();
}
public Cursor<byte[]> scan(ScanOptions options) {
return delegate.scan(options);
}
public void save() {
delegate.save();
}
public List<byte[]> hMGet(byte[] key, byte[]... fields) {
return delegate.hMGet(key, fields);
}
public Long zRem(byte[] key, byte[]... values) {
return delegate.zRem(key, values);
}
public Long lLen(byte[] key) {
return delegate.lLen(key);
}
public void unwatch() {
delegate.unwatch();
}
public Long dbSize() {
return delegate.dbSize();
}
public void setEx(byte[] key, long seconds, byte[] value) {
delegate.setEx(key, seconds, value);
}
public Long sCard(byte[] key) {
return delegate.sCard(key);
}
public byte[] randomKey() {
return delegate.randomKey();
}
public <T> T evalSha(String scriptSha, ReturnType returnType, int numKeys, byte[]... keysAndArgs) {
return delegate.evalSha(scriptSha, returnType, numKeys, keysAndArgs);
}
public List<byte[]> lRange(byte[] key, long begin, long end) {
return delegate.lRange(key, begin, end);
}
public void hMSet(byte[] key, Map<byte[], byte[]> hashes) {
delegate.hMSet(key, hashes);
}
public Double zIncrBy(byte[] key, double increment, byte[] value) {
return delegate.zIncrBy(key, increment, value);
}
public void flushDb() {
delegate.flushDb();
}
public Boolean sIsMember(byte[] key, byte[] value) {
return delegate.sIsMember(key, value);
}
public void pSubscribe(MessageListener listener, byte[]... patterns) {
delegate.pSubscribe(listener, patterns);
}
public void rename(byte[] oldName, byte[] newName) {
delegate.rename(oldName, newName);
}
public boolean isPipelined() {
return delegate.isPipelined();
}
public void pSetEx(byte[] key, long milliseconds, byte[] value) {
delegate.pSetEx(key, milliseconds, value);
}
public void flushAll() {
delegate.flushAll();
}
public void lTrim(byte[] key, long begin, long end) {
delegate.lTrim(key, begin, end);
}
public Long hIncrBy(byte[] key, byte[] field, long delta) {
return delegate.hIncrBy(key, field, delta);
}
public Set<byte[]> sInter(byte[]... keys) {
return delegate.sInter(keys);
}
public Boolean renameNX(byte[] oldName, byte[] newName) {
return delegate.renameNX(oldName, newName);
}
public Long zRank(byte[] key, byte[] value) {
return delegate.zRank(key, value);
}
public Properties info() {
return delegate.info();
}
public void openPipeline() {
delegate.openPipeline();
}
public void mSet(Map<byte[], byte[]> tuple) {
delegate.mSet(tuple);
}
public byte[] lIndex(byte[] key, long index) {
return delegate.lIndex(key, index);
}
public Long sInterStore(byte[] destKey, byte[]... keys) {
return delegate.sInterStore(destKey, keys);
}
public Double hIncrBy(byte[] key, byte[] field, double delta) {
return delegate.hIncrBy(key, field, delta);
}
public Long zRevRank(byte[] key, byte[] value) {
return delegate.zRevRank(key, value);
}
public Boolean expire(byte[] key, long seconds) {
return delegate.expire(key, seconds);
}
public Properties info(String section) {
return delegate.info(section);
}
public Boolean mSetNX(Map<byte[], byte[]> tuple) {
return delegate.mSetNX(tuple);
}
public Long lInsert(byte[] key, Position where, byte[] pivot, byte[] value) {
return delegate.lInsert(key, where, pivot, value);
}
public Set<byte[]> sUnion(byte[]... keys) {
return delegate.sUnion(keys);
}
public void shutdown() {
delegate.shutdown();
}
public Boolean pExpire(byte[] key, long millis) {
return delegate.pExpire(key, millis);
}
public Boolean hExists(byte[] key, byte[] field) {
return delegate.hExists(key, field);
}
public Set<byte[]> zRange(byte[] key, long begin, long end) {
return delegate.zRange(key, begin, end);
}
public void shutdown(ShutdownOption option) {
delegate.shutdown(option);
}
public Long sUnionStore(byte[] destKey, byte[]... keys) {
return delegate.sUnionStore(destKey, keys);
}
public Long incr(byte[] key) {
return delegate.incr(key);
}
public Long hDel(byte[] key, byte[]... fields) {
return delegate.hDel(key, fields);
}
public Boolean expireAt(byte[] key, long unixTime) {
return delegate.expireAt(key, unixTime);
}
public List<String> getConfig(String pattern) {
return delegate.getConfig(pattern);
}
public void lSet(byte[] key, long index, byte[] value) {
delegate.lSet(key, index, value);
}
public Set<Tuple> zRangeWithScores(byte[] key, long begin, long end) {
return delegate.zRangeWithScores(key, begin, end);
}
public Long incrBy(byte[] key, long value) {
return delegate.incrBy(key, value);
}
public Set<byte[]> sDiff(byte[]... keys) {
return delegate.sDiff(keys);
}
public Long hLen(byte[] key) {
return delegate.hLen(key);
}
public List<Object> closePipeline() throws RedisPipelineException {
return delegate.closePipeline();
}
public void setConfig(String param, String value) {
delegate.setConfig(param, value);
}
public Boolean pExpireAt(byte[] key, long unixTimeInMillis) {
return delegate.pExpireAt(key, unixTimeInMillis);
}
public Long lRem(byte[] key, long count, byte[] value) {
return delegate.lRem(key, count, value);
}
public Double incrBy(byte[] key, double value) {
return delegate.incrBy(key, value);
}
public Long sDiffStore(byte[] destKey, byte[]... keys) {
return delegate.sDiffStore(destKey, keys);
}
public Set<byte[]> zRangeByScore(byte[] key, double min, double max) {
return delegate.zRangeByScore(key, min, max);
}
public Set<byte[]> hKeys(byte[] key) {
return delegate.hKeys(key);
}
public void resetConfigStats() {
delegate.resetConfigStats();
}
public Long decr(byte[] key) {
return delegate.decr(key);
}
public List<byte[]> hVals(byte[] key) {
return delegate.hVals(key);
}
public Boolean persist(byte[] key) {
return delegate.persist(key);
}
public byte[] lPop(byte[] key) {
return delegate.lPop(key);
}
public Set<byte[]> sMembers(byte[] key) {
return delegate.sMembers(key);
}
public Long decrBy(byte[] key, long value) {
return delegate.decrBy(key, value);
}
public Set<Tuple> zRangeByScoreWithScores(byte[] key, double min, double max) {
return delegate.zRangeByScoreWithScores(key, min, max);
}
public Long time() {
return delegate.time();
}
public Map<byte[], byte[]> hGetAll(byte[] key) {
return delegate.hGetAll(key);
}
public Boolean move(byte[] key, int dbIndex) {
return delegate.move(key, dbIndex);
}
public byte[] sRandMember(byte[] key) {
return delegate.sRandMember(key);
}
public byte[] rPop(byte[] key) {
return delegate.rPop(key);
}
public void killClient(String host, int port) {
delegate.killClient(host, port);
}
public Long append(byte[] key, byte[] value) {
return delegate.append(key, value);
}
public Cursor<Entry<byte[], byte[]>> hScan(byte[] key, ScanOptions options) {
return delegate.hScan(key, options);
}
public List<byte[]> sRandMember(byte[] key, long count) {
return delegate.sRandMember(key, count);
}
public Long ttl(byte[] key) {
return delegate.ttl(key);
}
public List<byte[]> bLPop(int timeout, byte[]... keys) {
return delegate.bLPop(timeout, keys);
}
public Set<byte[]> zRangeByScore(byte[] key, double min, double max, long offset, long count) {
return delegate.zRangeByScore(key, min, max, offset, count);
}
public byte[] getRange(byte[] key, long begin, long end) {
return delegate.getRange(key, begin, end);
}
public void setClientName(byte[] name) {
delegate.setClientName(name);
}
public Long pTtl(byte[] key) {
return delegate.pTtl(key);
}
public Cursor<byte[]> sScan(byte[] key, ScanOptions options) {
return delegate.sScan(key, options);
}
public String getClientName() {
return delegate.getClientName();
}
public List<byte[]> sort(byte[] key, SortParameters params) {
return delegate.sort(key, params);
}
public List<byte[]> bRPop(int timeout, byte[]... keys) {
return delegate.bRPop(timeout, keys);
}
public void setRange(byte[] key, byte[] value, long offset) {
delegate.setRange(key, value, offset);
}
public Set<Tuple> zRangeByScoreWithScores(byte[] key, double min, double max, long offset, long count) {
return delegate.zRangeByScoreWithScores(key, min, max, offset, count);
}
public List<RedisClientInfo> getClientList() {
return delegate.getClientList();
}
public Long sort(byte[] key, SortParameters params, byte[] storeKey) {
return delegate.sort(key, params, storeKey);
}
public Boolean getBit(byte[] key, long offset) {
return delegate.getBit(key, offset);
}
public void slaveOf(String host, int port) {
delegate.slaveOf(host, port);
}
public byte[] rPopLPush(byte[] srcKey, byte[] dstKey) {
return delegate.rPopLPush(srcKey, dstKey);
}
public Set<byte[]> zRevRange(byte[] key, long begin, long end) {
return delegate.zRevRange(key, begin, end);
}
public byte[] dump(byte[] key) {
return delegate.dump(key);
}
public Boolean setBit(byte[] key, long offset, boolean value) {
return delegate.setBit(key, offset, value);
}
public void slaveOfNoOne() {
delegate.slaveOfNoOne();
}
public void restore(byte[] key, long ttlInMillis, byte[] serializedValue) {
delegate.restore(key, ttlInMillis, serializedValue);
}
public byte[] bRPopLPush(int timeout, byte[] srcKey, byte[] dstKey) {
return delegate.bRPopLPush(timeout, srcKey, dstKey);
}
public Set<Tuple> zRevRangeWithScores(byte[] key, long begin, long end) {
return delegate.zRevRangeWithScores(key, begin, end);
}
public Long bitCount(byte[] key) {
return delegate.bitCount(key);
}
public Set<byte[]> zRevRangeByScore(byte[] key, double min, double max) {
return delegate.zRevRangeByScore(key, min, max);
}
public Long bitCount(byte[] key, long begin, long end) {
return delegate.bitCount(key, begin, end);
}
public Set<Tuple> zRevRangeByScoreWithScores(byte[] key, double min, double max) {
return delegate.zRevRangeByScoreWithScores(key, min, max);
}
public Long bitOp(BitOperation op, byte[] destination, byte[]... keys) {
return delegate.bitOp(op, destination, keys);
}
public Long strLen(byte[] key) {
return delegate.strLen(key);
}
public Set<byte[]> zRevRangeByScore(byte[] key, double min, double max, long offset, long count) {
return delegate.zRevRangeByScore(key, min, max, offset, count);
}
public Set<Tuple> zRevRangeByScoreWithScores(byte[] key, double min, double max, long offset, long count) {
return delegate.zRevRangeByScoreWithScores(key, min, max, offset, count);
}
public Long zCount(byte[] key, double min, double max) {
return delegate.zCount(key, min, max);
}
public Long zCard(byte[] key) {
return delegate.zCard(key);
}
public Double zScore(byte[] key, byte[] value) {
return delegate.zScore(key, value);
}
public Long zRemRange(byte[] key, long begin, long end) {
return delegate.zRemRange(key, begin, end);
}
public Long zRemRangeByScore(byte[] key, double min, double max) {
return delegate.zRemRangeByScore(key, min, max);
}
public Long zUnionStore(byte[] destKey, byte[]... sets) {
return delegate.zUnionStore(destKey, sets);
}
public Long zUnionStore(byte[] destKey, Aggregate aggregate, int[] weights, byte[]... sets) {
return delegate.zUnionStore(destKey, aggregate, weights, sets);
}
public Long zInterStore(byte[] destKey, byte[]... sets) {
return delegate.zInterStore(destKey, sets);
}
public Long zInterStore(byte[] destKey, Aggregate aggregate, int[] weights, byte[]... sets) {
return delegate.zInterStore(destKey, aggregate, weights, sets);
}
public Cursor<Tuple> zScan(byte[] key, ScanOptions options) {
return delegate.zScan(key, options);
}
public RedisConnection getDelegate() {
return delegate;
}
@Override
protected RedisSentinelConnection getSentinelConnection(RedisNode sentinel) {
if (ObjectUtils.nullSafeEquals(this.activeNode, sentinel)) {
return this.sentinelConnection;
}
return null;
}
}
}

View File

@@ -30,6 +30,7 @@ import org.hamcrest.core.AllOf;
import org.hamcrest.core.IsCollectionContaining;
import org.hamcrest.core.IsInstanceOf;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.dao.InvalidDataAccessApiUsageException;
@@ -40,9 +41,13 @@ import org.springframework.data.redis.connection.DefaultStringTuple;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.connection.StringRedisConnection.StringTuple;
import org.springframework.data.redis.test.util.RedisSentinelRule;
import org.springframework.data.redis.test.util.RedisSentinelRule.SentinelsAvailable;
import org.springframework.data.redis.test.util.RelaxedJUnit4ClassRunner;
import org.springframework.data.redis.test.util.RequiresRedisSentinel;
import org.springframework.test.annotation.IfProfileValue;
import org.springframework.test.context.ContextConfiguration;
@@ -60,6 +65,8 @@ import redis.clients.jedis.JedisPoolConfig;
@ContextConfiguration
public class JedisConnectionIntegrationTests extends AbstractConnectionIntegrationTests {
public @Rule RedisSentinelRule sentinelRule = RedisSentinelRule.withDefaultConfig().dynamicModeSelection();
@After
public void tearDown() {
try {
@@ -366,4 +373,16 @@ public class JedisConnectionIntegrationTests extends AbstractConnectionIntegrati
assertTrue(String.format("difference between millis=%s and ttl=%s should not be greater than 20ms but is %s",
millis, ttl, millis - ttl), millis - ttl < 20L);
}
/**
* @see DATAREDIS-330
*/
@Test
@RequiresRedisSentinel(SentinelsAvailable.ONE_ACTIVE)
public void shouldReturnSentinelCommandsWhenWhenActiveSentinelFound() {
((JedisConnection) byteConnection).setSentinelConfiguration(new RedisSentinelConfiguration().master("mymaster")
.sentinel("127.0.0.1", 26379).sentinel("127.0.0.1", 26380));
assertThat(connection.getSentinelConnection(), notNullValue());
}
}

View File

@@ -29,6 +29,7 @@ import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
import org.mockito.ArgumentCaptor;
import org.springframework.dao.InvalidDataAccessResourceUsageException;
import org.springframework.data.redis.connection.AbstractConnectionUnitTestBase;
import org.springframework.data.redis.connection.RedisServerCommands.ShutdownOption;
import org.springframework.data.redis.connection.jedis.JedisConnectionUnitTestSuite.JedisConnectionPipelineUnitTests;
@@ -115,8 +116,6 @@ public class JedisConnectionUnitTestSuite {
}
/**
* <<<<<<< HEAD
*
* @see DATAREDIS-267
*/
@Test
@@ -163,6 +162,15 @@ public class JedisConnectionUnitTestSuite {
connection.slaveOfNoOne();
verifyNativeConnectionInvocation().slaveofNoOne();
}
/**
* @see DATAREDIS-330
*/
@Test(expected = InvalidDataAccessResourceUsageException.class)
public void shouldThrowExceptionWhenAccessingRedisSentinelsCommandsWhenNoSentinelsConfigured() {
connection.getSentinelConnection();
}
}
public static class JedisConnectionPipelineUnitTests extends JedisConnectionUnitTests {

View File

@@ -15,12 +15,19 @@
*/
package org.springframework.data.redis.connection.jedis;
import static org.hamcrest.core.Is.*;
import static org.hamcrest.core.IsEqual.*;
import static org.hamcrest.core.IsNull.*;
import static org.junit.Assert.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.springframework.data.redis.connection.RedisServer;
import org.springframework.data.redis.core.types.RedisClientInfo;
/**
@@ -59,4 +66,80 @@ public class JedisConvertersUnitTests {
assertThat(JedisConverters.toListOfRedisClientInformation(sb.toString()).size(), equalTo(2));
}
/**
* @see DATAREDIS-330
*/
@Test
public void convertsSingleMapToRedisServerReturnsCollectionCorrectly() {
Map<String, String> values = getRedisServerInfoMap("mymaster", 23697);
List<RedisServer> servers = JedisConverters.toListOfRedisServer(Collections.singletonList(values));
assertThat(servers.size(), is(1));
verifyRedisServerInfo(servers.get(0), values);
}
/**
* @see DATAREDIS-330
*/
@Test
public void convertsMultipleMapsToRedisServerReturnsCollectionCorrectly() {
List<Map<String, String>> vals = Arrays.asList(getRedisServerInfoMap("mymaster", 23697),
getRedisServerInfoMap("yourmaster", 23680));
List<RedisServer> servers = JedisConverters.toListOfRedisServer(vals);
assertThat(servers.size(), is(vals.size()));
for (int i = 0; i < vals.size(); i++) {
verifyRedisServerInfo(servers.get(i), vals.get(i));
}
}
/**
* @see DATAREDIS-330
*/
@Test
public void convertsRedisServersCorrectlyWhenGivenAnEmptyList() {
assertThat(JedisConverters.toListOfRedisServer(Collections.<Map<String, String>> emptyList()), notNullValue());
}
/**
* @see DATAREDIS-330
*/
@Test
public void convertsRedisServersCorrectlyWhenGivenNull() {
assertThat(JedisConverters.toListOfRedisServer(null), notNullValue());
}
private void verifyRedisServerInfo(RedisServer server, Map<String, String> values) {
for (Map.Entry<String, String> entry : values.entrySet()) {
assertThat(server.get(entry.getKey()), equalTo(entry.getValue()));
}
}
private Map<String, String> getRedisServerInfoMap(String name, int port) {
Map<String, String> map = new HashMap<String, String>();
map.put("name", name);
map.put("ip", "127.0.0.1");
map.put("port", Integer.toString(port));
map.put("runid", "768c2926e5148d208713bf17cd5821e10f5388e2");
map.put("flags", "master");
map.put("pending-commands", "0");
map.put("last-ping-sent", "0");
map.put("last-ok-ping-reply", "534");
map.put("last-ping-reply", "534");
map.put("down-after-milliseconds", "30000");
map.put("info-refresh", "147");
map.put("role-reported", "master");
map.put("role-reported-time", "41248339");
map.put("config-epoch", "7");
map.put("num-slaves", "2");
map.put("num-other-sentinels", "2");
map.put("quorum", "2");
map.put("failover-timeout", "180000");
map.put("parallel-syncs", "1");
return map;
}
}

View File

@@ -0,0 +1,198 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.redis.connection.jedis;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.RedisNode.RedisNodeBuilder;
import org.springframework.data.redis.connection.RedisServer;
import redis.clients.jedis.Jedis;
/**
* @author Christoph Strobl
*/
@RunWith(MockitoJUnitRunner.class)
public class JedisSentinelConnectionUnitTests {
private @Mock Jedis jedisMock;
private JedisSentinelConnection connection;
@Before
public void setUp() {
this.connection = new JedisSentinelConnection(jedisMock);
}
/**
* @see DATAREDIS-330
*/
@Test
public void shouldConnectAfterCreation() {
verify(jedisMock, times(1)).connect();
}
/**
* @see DATAREDIS-330
*/
@SuppressWarnings("resource")
@Test
public void shouldNotConnectIfAlreadyConnected() {
Jedis yetAnotherJedisMock = mock(Jedis.class);
when(yetAnotherJedisMock.isConnected()).thenReturn(true);
new JedisSentinelConnection(yetAnotherJedisMock);
verify(yetAnotherJedisMock, never()).connect();
}
/**
* @see DATAREDIS-330
*/
@Test
public void failoverShouldBeSentCorrectly() {
connection.failover(new RedisNodeBuilder().withName("mymaster").build());
verify(jedisMock, times(1)).sentinelFailover(eq("mymaster"));
}
/**
* @see DATAREDIS-330
*/
@Test(expected = IllegalArgumentException.class)
public void failoverShouldThrowExceptionIfMasterNodeIsNull() {
connection.failover(null);
}
/**
* @see DATAREDIS-330
*/
@Test(expected = IllegalArgumentException.class)
public void failoverShouldThrowExceptionIfMasterNodeNameIsEmpty() {
connection.failover(new RedisNodeBuilder().build());
}
/**
* @see DATAREDIS-330
*/
@Test
public void mastersShouldReadMastersCorrectly() {
connection.masters();
verify(jedisMock, times(1)).sentinelMasters();
}
/**
* @see DATAREDIS-330
*/
@Test
public void shouldReadSlavesCorrectly() {
connection.slaves("mymaster");
verify(jedisMock, times(1)).sentinelSlaves(eq("mymaster"));
}
/**
* @see DATAREDIS-330
*/
@Test
public void shouldReadSlavesCorrectlyWhenGivenNamedNode() {
connection.slaves(new RedisNodeBuilder().withName("mymaster").build());
verify(jedisMock, times(1)).sentinelSlaves(eq("mymaster"));
}
/**
* @see DATAREDIS-330
*/
@Test(expected = IllegalArgumentException.class)
public void readSlavesShouldThrowExceptionWhenGivenEmptyMasterName() {
connection.slaves("");
}
/**
* @see DATAREDIS-330
*/
@Test(expected = IllegalArgumentException.class)
public void readSlavesShouldThrowExceptionWhenGivenNull() {
connection.slaves((RedisNode) null);
}
/**
* @see DATAREDIS-330
*/
@Test(expected = IllegalArgumentException.class)
public void readSlavesShouldThrowExceptionWhenNodeWithoutName() {
connection.slaves(new RedisNodeBuilder().build());
}
/**
* @see DATAREDIS-330
*/
@Test
public void shouldRemoveMasterCorrectlyWhenGivenNamedNode() {
connection.remove(new RedisNodeBuilder().withName("mymaster").build());
verify(jedisMock, times(1)).sentinelRemove(eq("mymaster"));
}
/**
* @see DATAREDIS-330
*/
@Test(expected = IllegalArgumentException.class)
public void removeShouldThrowExceptionWhenGivenEmptyMasterName() {
connection.remove("");
}
/**
* @see DATAREDIS-330
*/
@Test(expected = IllegalArgumentException.class)
public void removeShouldThrowExceptionWhenGivenNull() {
connection.remove((RedisNode) null);
}
/**
* @see DATAREDIS-330
*/
@Test(expected = IllegalArgumentException.class)
public void removeShouldThrowExceptionWhenNodeWithoutName() {
connection.remove(new RedisNodeBuilder().build());
}
/**
* @see DATAREDIS-330
*/
@Test
public void monitorShouldBeSentCorrectly() {
RedisServer server = new RedisServer("127.0.0.1", 6382);
server.setName("anothermaster");
server.setQuorum(3L);
connection.monitor(server);
verify(jedisMock, times(1)).sentinelMonitor(eq("anothermaster"), eq("127.0.0.1"), eq(6382), eq(3));
}
}

View File

@@ -15,6 +15,12 @@
*/
package org.springframework.data.redis.connection.jedis;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.assertThat;
import java.util.Collection;
import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
@@ -23,17 +29,29 @@ import org.junit.Test;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.redis.connection.AbstractConnectionIntegrationTests;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.RedisSentinelConnection;
import org.springframework.data.redis.connection.RedisServer;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.test.util.RedisSentinelRule;
import org.springframework.test.annotation.IfProfileValue;
/**
* @author Christoph Strobl
* @author Thomas Darimont
*/
public class JedisSentinelIntegrationTests extends AbstractConnectionIntegrationTests {
private static final RedisSentinelConfiguration SENTINEL_CONFIG = new RedisSentinelConfiguration().master("mymaster")
.sentinel("127.0.0.1", 26379).sentinel("127.0.0.1", 26380);
private static final String MASTER_NAME = "mymaster";
private static final RedisServer SENTINEL_0 = new RedisServer("127.0.0.1", 26379);
private static final RedisServer SENTINEL_1 = new RedisServer("127.0.0.1", 26380);
private static final RedisServer SLAVE_0 = new RedisServer("127.0.0.1", 6380);
private static final RedisServer SLAVE_1 = new RedisServer("127.0.0.1", 6381);
private static final RedisSentinelConfiguration SENTINEL_CONFIG = new RedisSentinelConfiguration() //
.master(MASTER_NAME)
.sentinel(SENTINEL_0)
.sentinel(SENTINEL_1);
public @Rule RedisSentinelRule sentinelRule = RedisSentinelRule.forConfig(SENTINEL_CONFIG).oneActive();
@@ -102,4 +120,32 @@ public class JedisSentinelIntegrationTests extends AbstractConnectionIntegration
public void testErrorInTx() {
super.testErrorInTx();
}
/**
* @see DATAREDIS-330
*/
@Test
public void shouldReadMastersCorrectly() {
List<RedisServer> servers = (List<RedisServer>) connectionFactory.getSentinelConnection().masters();
assertThat(servers.size(), is(1));
assertThat(servers.get(0).getName(),is(MASTER_NAME));
}
/**
* @see DATAREDIS-330
*/
@Test
public void shouldReadSlavesOfMastersCorrectly() {
RedisSentinelConnection sentinelConnection = connectionFactory.getSentinelConnection();
List<RedisServer> servers = (List<RedisServer>) sentinelConnection.masters();
assertThat(servers.size(), is(1));
Collection<RedisServer> slaves = sentinelConnection.slaves(servers.get(0));
assertThat(slaves.size(), is(2));
assertThat(slaves, hasItems(SLAVE_0, SLAVE_1));
}
}

View File

@@ -29,24 +29,18 @@ import redis.clients.jedis.Jedis;
*/
public class RedisSentinelRule implements TestRule {
enum VerificationMode {
ALL_ACTIVE, ONE_ACTIVE, NO_SENTINEL
public enum SentinelsAvailable {
ALL_ACTIVE, ONE_ACTIVE, NONE_ACTIVE
}
private static final RedisSentinelConfiguration DEFAULT_SENTINEL_CONFIG = new RedisSentinelConfiguration()
.master("mymaster").sentinel("127.0.0.1", 26379).sentinel("127.0.0.1", 26380).sentinel("127.0.0.1", 26381);
private RedisSentinelConfiguration sentinelConfig;
private VerificationMode mode;
private SentinelsAvailable requiredSentinels;
protected RedisSentinelRule(RedisSentinelConfiguration config) {
this(config, VerificationMode.ALL_ACTIVE);
}
protected RedisSentinelRule(RedisSentinelConfiguration config, VerificationMode mode) {
this.sentinelConfig = config;
this.mode = mode;
}
/**
@@ -69,8 +63,8 @@ public class RedisSentinelRule implements TestRule {
}
public RedisSentinelRule sentinelsDisabled() {
this.mode = VerificationMode.NO_SENTINEL;
this.requiredSentinels = SentinelsAvailable.NONE_ACTIVE;
return this;
}
@@ -80,8 +74,8 @@ public class RedisSentinelRule implements TestRule {
* @return
*/
public RedisSentinelRule allActive() {
this.mode = VerificationMode.ALL_ACTIVE;
this.requiredSentinels = SentinelsAvailable.ALL_ACTIVE;
return this;
}
@@ -91,25 +85,46 @@ public class RedisSentinelRule implements TestRule {
* @return
*/
public RedisSentinelRule oneActive() {
this.mode = VerificationMode.ONE_ACTIVE;
this.requiredSentinels = SentinelsAvailable.ONE_ACTIVE;
return this;
}
/**
* Will only check {@link RedisSentinelConfiguration} configuration in case {@link RequiresRedisSentinel} is detected
* on test method.
*
* @return
*/
public RedisSentinelRule dynamicModeSelection() {
this.requiredSentinels = null;
return this;
}
@Override
public Statement apply(final Statement base, Description description) {
public Statement apply(final Statement base, final Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
verify();
if (description.isTest()) {
RequiresRedisSentinel sentinels = description.getAnnotation(RequiresRedisSentinel.class);
if (RedisSentinelRule.this.requiredSentinels != null || sentinels != null) {
verify(sentinels != null ? sentinels.value() : RedisSentinelRule.this.requiredSentinels);
}
} else {
verify(RedisSentinelRule.this.requiredSentinels);
}
base.evaluate();
}
};
}
private void verify() {
private void verify(SentinelsAvailable verificationMode) {
int failed = 0;
for (RedisNode node : sentinelConfig.getSentinels()) {
@@ -119,19 +134,19 @@ public class RedisSentinelRule implements TestRule {
}
if (failed > 0) {
if (VerificationMode.ALL_ACTIVE.equals(mode)) {
if (SentinelsAvailable.ALL_ACTIVE.equals(verificationMode)) {
throw new AssumptionViolatedException(String.format(
"Expected all Redis Sentinels to respone but %s of %s did not responde", failed, sentinelConfig
.getSentinels().size()));
}
if (VerificationMode.ONE_ACTIVE.equals(mode) && sentinelConfig.getSentinels().size() - 1 < failed) {
if (SentinelsAvailable.ONE_ACTIVE.equals(verificationMode) && sentinelConfig.getSentinels().size() - 1 < failed) {
throw new AssumptionViolatedException(
"Expected at least one sentinel to respond but it seems all are offline - Game Over!");
}
}
if (VerificationMode.NO_SENTINEL.equals(mode) && failed != sentinelConfig.getSentinels().size()) {
if (SentinelsAvailable.NONE_ACTIVE.equals(verificationMode) && failed != sentinelConfig.getSentinels().size()) {
throw new AssumptionViolatedException(String.format(
"Expected to have no sentinels online but found that %s are still alive.", (sentinelConfig.getSentinels()
.size() - failed)));
@@ -139,30 +154,30 @@ public class RedisSentinelRule implements TestRule {
}
private boolean isAvailable(RedisNode node) {
Jedis jedis = null;
try {
jedis = new Jedis(node.getHost(), node.getPort());
jedis.connect();
jedis.ping();
} catch (Exception e) {
return false;
} finally {
if (jedis != null) {
try {
jedis.disconnect();
jedis.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
return true;
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.redis.test.util;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.data.redis.test.util.RedisSentinelRule.SentinelsAvailable;
/**
* @author Christoph Strobl
*/
@Documented
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresRedisSentinel {
SentinelsAvailable value() default SentinelsAvailable.ALL_ACTIVE;
}