Support sentinel username in SentinelConfiguration.
Closes #2218 Original pull request: #2224.
This commit is contained in:
@@ -31,6 +31,7 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Luis De Bello
|
||||
* @author Vikas Garg
|
||||
* @since 2.1
|
||||
*/
|
||||
public interface RedisConfiguration {
|
||||
@@ -438,6 +439,21 @@ public interface RedisConfiguration {
|
||||
* @since 2.2.2
|
||||
*/
|
||||
RedisPassword getSentinelPassword();
|
||||
|
||||
/**
|
||||
* Create and set a username with the given {@link String}. Requires Redis 6 or newer.
|
||||
*
|
||||
* @param sentinelUsername the username for sentinel.
|
||||
*/
|
||||
void setSentinelUsername(@Nullable String sentinelUsername);
|
||||
|
||||
/**
|
||||
* Get the username to use when connecting.
|
||||
*
|
||||
* @return {@literal null} if none set.
|
||||
*/
|
||||
@Nullable
|
||||
String getSentinelUsername();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -39,6 +39,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author Christoph Strobl
|
||||
* @author Thomas Darimont
|
||||
* @author Mark Paluch
|
||||
* @author Vikas Garg
|
||||
* @since 1.4
|
||||
*/
|
||||
public class RedisSentinelConfiguration implements RedisConfiguration, SentinelConfiguration {
|
||||
@@ -46,12 +47,14 @@ public class RedisSentinelConfiguration implements RedisConfiguration, SentinelC
|
||||
private static final String REDIS_SENTINEL_MASTER_CONFIG_PROPERTY = "spring.redis.sentinel.master";
|
||||
private static final String REDIS_SENTINEL_NODES_CONFIG_PROPERTY = "spring.redis.sentinel.nodes";
|
||||
private static final String REDIS_SENTINEL_PASSWORD_CONFIG_PROPERTY = "spring.redis.sentinel.password";
|
||||
private static final String REDIS_SENTINEL_USERNAME_CONFIG_PROPERTY = "spring.redis.sentinel.username";
|
||||
|
||||
private @Nullable NamedNode master;
|
||||
private Set<RedisNode> sentinels;
|
||||
private int database;
|
||||
|
||||
private @Nullable String dataNodeUsername = null;
|
||||
private @Nullable String sentinelUsername = null;
|
||||
private RedisPassword dataNodePassword = RedisPassword.none();
|
||||
private RedisPassword sentinelPassword = RedisPassword.none();
|
||||
|
||||
@@ -107,6 +110,10 @@ public class RedisSentinelConfiguration implements RedisConfiguration, SentinelC
|
||||
if (propertySource.containsProperty(REDIS_SENTINEL_PASSWORD_CONFIG_PROPERTY)) {
|
||||
this.setSentinelPassword(propertySource.getProperty(REDIS_SENTINEL_PASSWORD_CONFIG_PROPERTY).toString());
|
||||
}
|
||||
|
||||
if (propertySource.containsProperty(REDIS_SENTINEL_USERNAME_CONFIG_PROPERTY)) {
|
||||
this.setSentinelUsername(propertySource.getProperty(REDIS_SENTINEL_USERNAME_CONFIG_PROPERTY).toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -270,6 +277,25 @@ public class RedisSentinelConfiguration implements RedisConfiguration, SentinelC
|
||||
this.dataNodePassword = password;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.RedisConfiguration.SentinelConfiguration#getSentinelUsername()
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public String getSentinelUsername() {
|
||||
return this.sentinelUsername;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.RedisConfiguration.SentinelConfiguration#setSentinelUsername(String)
|
||||
*/
|
||||
@Override
|
||||
public void setSentinelUsername(@Nullable String sentinelUsername) {
|
||||
this.sentinelUsername = sentinelUsername;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.RedisConfiguration.SentinelConfiguration#setSentinelPassword(org.springframework.data.redis.connection.RedisPassword)
|
||||
|
||||
@@ -82,6 +82,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author Ninad Divadkar
|
||||
* @author dengliming
|
||||
* @author Chris Bono
|
||||
* @author Vikas Garg
|
||||
*/
|
||||
public abstract class LettuceConverters extends Converters {
|
||||
|
||||
@@ -512,7 +513,13 @@ public abstract class LettuceConverters extends Converters {
|
||||
|
||||
RedisURI.Builder sentinelBuilder = RedisURI.Builder.redis(sentinel.getHost(), sentinel.getPort());
|
||||
|
||||
sentinelPassword.toOptional().ifPresent(sentinelBuilder::withPassword);
|
||||
String sentinelUsername = sentinelConfiguration.getSentinelUsername();
|
||||
if (StringUtils.hasText(sentinelUsername) && sentinelPassword.isPresent()) {
|
||||
// See https://github.com/lettuce-io/lettuce-core/issues/1404
|
||||
sentinelBuilder.withAuthentication(sentinelUsername, new String(sentinelPassword.toOptional().orElse((new char[0]))));
|
||||
} else {
|
||||
sentinelPassword.toOptional().ifPresent(sentinelBuilder::withPassword);
|
||||
}
|
||||
|
||||
builder.withSentinel(sentinelBuilder.build());
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @author Vikas Garg
|
||||
*/
|
||||
class RedisSentinelConfigurationUnitTests {
|
||||
|
||||
@@ -67,20 +68,20 @@ class RedisSentinelConfigurationUnitTests {
|
||||
@Test // DATAREDIS-372
|
||||
void shouldThrowExceptionWhenListOfHostAndPortIsNull() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new RedisSentinelConfiguration("mymaster", Collections.<String> singleton(null)));
|
||||
.isThrownBy(() -> new RedisSentinelConfiguration("mymaster", Collections.singleton(null)));
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-372
|
||||
void shouldNotFailWhenListOfHostAndPortIsEmpty() {
|
||||
|
||||
RedisSentinelConfiguration config = new RedisSentinelConfiguration("mymaster", Collections.<String> emptySet());
|
||||
RedisSentinelConfiguration config = new RedisSentinelConfiguration("mymaster", Collections.emptySet());
|
||||
|
||||
assertThat(config.getSentinels()).isEmpty();
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-372
|
||||
void shouldThrowExceptionGivenNullPropertySource() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new RedisSentinelConfiguration((PropertySource<?>) null));
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new RedisSentinelConfiguration(null));
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-372
|
||||
@@ -93,7 +94,7 @@ class RedisSentinelConfigurationUnitTests {
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-372
|
||||
void shouldBeCreatedCorrecltyGivenValidPropertySourceWithMasterAndSingleHostPort() {
|
||||
void shouldBeCreatedCorrectlyGivenValidPropertySourceWithMasterAndSingleHostPort() {
|
||||
|
||||
MockPropertySource propertySource = new MockPropertySource();
|
||||
propertySource.setProperty("spring.redis.sentinel.master", "myMaster");
|
||||
@@ -101,13 +102,14 @@ class RedisSentinelConfigurationUnitTests {
|
||||
|
||||
RedisSentinelConfiguration config = new RedisSentinelConfiguration(propertySource);
|
||||
|
||||
assertThat(config.getMaster()).isNotNull();
|
||||
assertThat(config.getMaster().getName()).isEqualTo("myMaster");
|
||||
assertThat(config.getSentinels()).hasSize(1);
|
||||
assertThat(config.getSentinels()).contains(new RedisNode("127.0.0.1", 123));
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-372
|
||||
void shouldBeCreatedCorrecltyGivenValidPropertySourceWithMasterAndMultipleHostPort() {
|
||||
void shouldBeCreatedCorrectlyGivenValidPropertySourceWithMasterAndMultipleHostPort() {
|
||||
|
||||
MockPropertySource propertySource = new MockPropertySource();
|
||||
propertySource.setProperty("spring.redis.sentinel.master", "myMaster");
|
||||
@@ -132,6 +134,16 @@ class RedisSentinelConfigurationUnitTests {
|
||||
assertThat(configuration.getSentinelPassword()).isEqualTo(RedisPassword.none());
|
||||
}
|
||||
|
||||
@Test
|
||||
void dataNodeUsernameDoesNotAffectSentinelUsername() {
|
||||
RedisSentinelConfiguration configuration = new RedisSentinelConfiguration("myMaster",
|
||||
Collections.singleton(HOST_AND_PORT_1));
|
||||
configuration.setSentinelUsername("sentinel-admin");
|
||||
configuration.setUsername("app");
|
||||
|
||||
assertThat(configuration.getSentinelUsername()).isEqualTo("sentinel-admin");
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-1060
|
||||
void readSentinelPasswordFromConfigProperty() {
|
||||
|
||||
@@ -145,4 +157,18 @@ class RedisSentinelConfigurationUnitTests {
|
||||
assertThat(config.getSentinelPassword()).isEqualTo(RedisPassword.of("computer-says-no"));
|
||||
assertThat(config.getSentinels()).hasSize(1).contains(new RedisNode("127.0.0.1", 123));
|
||||
}
|
||||
|
||||
@Test
|
||||
void readSentinelUsernameFromConfigProperty() {
|
||||
|
||||
MockPropertySource propertySource = new MockPropertySource();
|
||||
propertySource.setProperty("spring.redis.sentinel.master", "myMaster");
|
||||
propertySource.setProperty("spring.redis.sentinel.nodes", HOST_AND_PORT_1);
|
||||
propertySource.setProperty("spring.redis.sentinel.username", "sentinel-admin");
|
||||
|
||||
RedisSentinelConfiguration config = new RedisSentinelConfiguration(propertySource);
|
||||
|
||||
assertThat(config.getSentinelUsername()).isEqualTo(RedisPassword.of("sentinel-admin"));
|
||||
assertThat(config.getSentinels()).hasSize(1).contains(new RedisNode("127.0.0.1", 123));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,20 +37,23 @@ import org.junit.jupiter.api.Test;
|
||||
import org.springframework.data.redis.connection.RedisClusterNode;
|
||||
import org.springframework.data.redis.connection.RedisClusterNode.Flag;
|
||||
import org.springframework.data.redis.connection.RedisClusterNode.LinkState;
|
||||
import org.springframework.data.redis.connection.RedisPassword;
|
||||
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
|
||||
import org.springframework.data.redis.connection.RedisStringCommands.SetOption;
|
||||
import org.springframework.data.redis.connection.RedisZSetCommands;
|
||||
import org.springframework.data.redis.connection.jedis.JedisConverters;
|
||||
import org.springframework.data.redis.core.types.Expiration;
|
||||
import org.springframework.data.redis.core.types.RedisClientInfo;
|
||||
import redis.clients.jedis.params.SetParams;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @author Vikas Garg
|
||||
*/
|
||||
class LettuceConvertersUnitTests {
|
||||
|
||||
private static final String CLIENT_ALL_SINGLE_LINE_RESPONSE = "addr=127.0.0.1:60311 fd=6 name= age=4059 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=32768 obl=0 oll=0 omem=0 events=r cmd=client";
|
||||
|
||||
private static final String MASTER_NAME = "mymaster";
|
||||
|
||||
@Test // DATAREDIS-268
|
||||
void convertingEmptyStringToListOfRedisClientInfoShouldReturnEmptyList() {
|
||||
assertThat(LettuceConverters.toListOfRedisClientInformation(""))
|
||||
@@ -75,12 +78,12 @@ class LettuceConvertersUnitTests {
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-315
|
||||
void partitionsToClusterNodesShouldReturnEmptyCollectionWhenPartionsDoesNotContainElements() {
|
||||
void partitionsToClusterNodesShouldReturnEmptyCollectionWhenPartitionsDoesNotContainElements() {
|
||||
assertThat(LettuceConverters.partitionsToClusterNodes(new Partitions())).isNotNull();
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-315
|
||||
void partitionsToClusterNodesShouldConvertPartitionCorrctly() {
|
||||
void partitionsToClusterNodesShouldConvertPartitionCorrectly() {
|
||||
|
||||
Partitions partitions = new Partitions();
|
||||
|
||||
@@ -89,7 +92,7 @@ class LettuceConvertersUnitTests {
|
||||
partition.setConnected(true);
|
||||
partition.setFlags(new HashSet<>(Arrays.asList(NodeFlag.MASTER, NodeFlag.MYSELF)));
|
||||
partition.setUri(RedisURI.create("redis://" + CLUSTER_HOST + ":" + MASTER_NODE_1_PORT));
|
||||
partition.setSlots(Arrays.<Integer> asList(1, 2, 3, 4, 5));
|
||||
partition.setSlots(Arrays.asList(1, 2, 3, 4, 5));
|
||||
|
||||
partitions.add(partition);
|
||||
|
||||
@@ -252,4 +255,58 @@ class LettuceConvertersUnitTests {
|
||||
assertThatCommandArgument(LettuceConverters.toGetExArgs(Expiration.unixTimestamp(10, TimeUnit.MILLISECONDS)))
|
||||
.isEqualTo(new GetExArgs().pxAt(10));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sentinelConfigurationWithAuth() {
|
||||
RedisPassword password = RedisPassword.of("88888888-8x8-getting-creative-now");
|
||||
RedisSentinelConfiguration sentinelConfiguration = new RedisSentinelConfiguration()
|
||||
.master(MASTER_NAME)
|
||||
.sentinel("127.0.0.1", 26379)
|
||||
.sentinel("127.0.0.1", 26380);
|
||||
sentinelConfiguration.setSentinelUsername("admin");
|
||||
sentinelConfiguration.setSentinelPassword(password);
|
||||
sentinelConfiguration.setUsername("app");
|
||||
sentinelConfiguration.setPassword(password);
|
||||
RedisURI redisURI = LettuceConverters.sentinelConfigurationToRedisURI(sentinelConfiguration);
|
||||
assertThat(redisURI.getUsername()).isEqualTo("app");
|
||||
redisURI.getSentinels().forEach(sentinel -> {
|
||||
assertThat(sentinel.getUsername()).isEqualTo("admin");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void sentinelConfigurationSetSentinelPasswordIfUsernameNotPresent() {
|
||||
RedisPassword password = RedisPassword.of("88888888-8x8-getting-creative-now");
|
||||
RedisSentinelConfiguration sentinelConfiguration = new RedisSentinelConfiguration()
|
||||
.master(MASTER_NAME)
|
||||
.sentinel("127.0.0.1", 26379)
|
||||
.sentinel("127.0.0.1", 26380);
|
||||
sentinelConfiguration.setSentinelPassword(password);
|
||||
sentinelConfiguration.setUsername("app");
|
||||
sentinelConfiguration.setPassword(password);
|
||||
RedisURI redisURI = LettuceConverters.sentinelConfigurationToRedisURI(sentinelConfiguration);
|
||||
assertThat(redisURI.getUsername()).isEqualTo("app");
|
||||
redisURI.getSentinels().forEach(sentinel -> {
|
||||
assertThat(sentinel.getUsername()).isNull();
|
||||
assertThat(sentinel.getPassword()).isNotNull();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void sentinelConfigurationShouldNotSetSentinelAuthIfUsernameIsPresentWithNoPassword() {
|
||||
RedisPassword password = RedisPassword.of("88888888-8x8-getting-creative-now");
|
||||
RedisSentinelConfiguration sentinelConfiguration = new RedisSentinelConfiguration()
|
||||
.master(MASTER_NAME)
|
||||
.sentinel("127.0.0.1", 26379)
|
||||
.sentinel("127.0.0.1", 26380);
|
||||
sentinelConfiguration.setSentinelUsername("admin");
|
||||
sentinelConfiguration.setUsername("app");
|
||||
sentinelConfiguration.setPassword(password);
|
||||
RedisURI redisURI = LettuceConverters.sentinelConfigurationToRedisURI(sentinelConfiguration);
|
||||
assertThat(redisURI.getUsername()).isEqualTo("app");
|
||||
redisURI.getSentinels().forEach(sentinel -> {
|
||||
assertThat(sentinel.getUsername()).isNull();
|
||||
assertThat(sentinel.getPassword()).isNull();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user