Skip to content

Commit f333b18

Browse files
vikasgarghbmp911de
authored andcommitted
Support sentinel username in SentinelConfiguration.
Closes #2218 Original pull request: #2224.
1 parent be1663c commit f333b18

File tree

5 files changed

+143
-11
lines changed

5 files changed

+143
-11
lines changed

src/main/java/org/springframework/data/redis/connection/RedisConfiguration.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
*
3232
* @author Christoph Strobl
3333
* @author Luis De Bello
34+
* @author Vikas Garg
3435
* @since 2.1
3536
*/
3637
public interface RedisConfiguration {
@@ -438,6 +439,21 @@ default void setSentinelPassword(@Nullable char[] password) {
438439
* @since 2.2.2
439440
*/
440441
RedisPassword getSentinelPassword();
442+
443+
/**
444+
* Create and set a username with the given {@link String}. Requires Redis 6 or newer.
445+
*
446+
* @param sentinelUsername the username for sentinel.
447+
*/
448+
void setSentinelUsername(@Nullable String sentinelUsername);
449+
450+
/**
451+
* Get the username to use when connecting.
452+
*
453+
* @return {@literal null} if none set.
454+
*/
455+
@Nullable
456+
String getSentinelUsername();
441457
}
442458

443459
/**

src/main/java/org/springframework/data/redis/connection/RedisSentinelConfiguration.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,19 +39,22 @@
3939
* @author Christoph Strobl
4040
* @author Thomas Darimont
4141
* @author Mark Paluch
42+
* @author Vikas Garg
4243
* @since 1.4
4344
*/
4445
public class RedisSentinelConfiguration implements RedisConfiguration, SentinelConfiguration {
4546

4647
private static final String REDIS_SENTINEL_MASTER_CONFIG_PROPERTY = "spring.redis.sentinel.master";
4748
private static final String REDIS_SENTINEL_NODES_CONFIG_PROPERTY = "spring.redis.sentinel.nodes";
4849
private static final String REDIS_SENTINEL_PASSWORD_CONFIG_PROPERTY = "spring.redis.sentinel.password";
50+
private static final String REDIS_SENTINEL_USERNAME_CONFIG_PROPERTY = "spring.redis.sentinel.username";
4951

5052
private @Nullable NamedNode master;
5153
private Set<RedisNode> sentinels;
5254
private int database;
5355

5456
private @Nullable String dataNodeUsername = null;
57+
private @Nullable String sentinelUsername = null;
5558
private RedisPassword dataNodePassword = RedisPassword.none();
5659
private RedisPassword sentinelPassword = RedisPassword.none();
5760

@@ -107,6 +110,10 @@ public RedisSentinelConfiguration(PropertySource<?> propertySource) {
107110
if (propertySource.containsProperty(REDIS_SENTINEL_PASSWORD_CONFIG_PROPERTY)) {
108111
this.setSentinelPassword(propertySource.getProperty(REDIS_SENTINEL_PASSWORD_CONFIG_PROPERTY).toString());
109112
}
113+
114+
if (propertySource.containsProperty(REDIS_SENTINEL_USERNAME_CONFIG_PROPERTY)) {
115+
this.setSentinelUsername(propertySource.getProperty(REDIS_SENTINEL_USERNAME_CONFIG_PROPERTY).toString());
116+
}
110117
}
111118

112119
/**
@@ -270,6 +277,25 @@ public void setPassword(RedisPassword password) {
270277
this.dataNodePassword = password;
271278
}
272279

280+
/*
281+
* (non-Javadoc)
282+
* @see org.springframework.data.redis.connection.RedisConfiguration.SentinelConfiguration#getSentinelUsername()
283+
*/
284+
@Nullable
285+
@Override
286+
public String getSentinelUsername() {
287+
return this.sentinelUsername;
288+
}
289+
290+
/*
291+
* (non-Javadoc)
292+
* @see org.springframework.data.redis.connection.RedisConfiguration.SentinelConfiguration#setSentinelUsername(String)
293+
*/
294+
@Override
295+
public void setSentinelUsername(@Nullable String sentinelUsername) {
296+
this.sentinelUsername = sentinelUsername;
297+
}
298+
273299
/*
274300
* (non-Javadoc)
275301
* @see org.springframework.data.redis.connection.RedisConfiguration.SentinelConfiguration#setSentinelPassword(org.springframework.data.redis.connection.RedisPassword)

src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConverters.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
* @author Ninad Divadkar
8383
* @author dengliming
8484
* @author Chris Bono
85+
* @author Vikas Garg
8586
*/
8687
public abstract class LettuceConverters extends Converters {
8788

@@ -512,7 +513,13 @@ public static RedisURI sentinelConfigurationToRedisURI(RedisSentinelConfiguratio
512513

513514
RedisURI.Builder sentinelBuilder = RedisURI.Builder.redis(sentinel.getHost(), sentinel.getPort());
514515

515-
sentinelPassword.toOptional().ifPresent(sentinelBuilder::withPassword);
516+
String sentinelUsername = sentinelConfiguration.getSentinelUsername();
517+
if (StringUtils.hasText(sentinelUsername) && sentinelPassword.isPresent()) {
518+
// See https://github.com/lettuce-io/lettuce-core/issues/1404
519+
sentinelBuilder.withAuthentication(sentinelUsername, new String(sentinelPassword.toOptional().orElse((new char[0]))));
520+
} else {
521+
sentinelPassword.toOptional().ifPresent(sentinelBuilder::withPassword);
522+
}
516523

517524
builder.withSentinel(sentinelBuilder.build());
518525
}

src/test/java/org/springframework/data/redis/connection/RedisSentinelConfigurationUnitTests.java

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
/**
3131
* @author Christoph Strobl
32+
* @author Vikas Garg
3233
*/
3334
class RedisSentinelConfigurationUnitTests {
3435

@@ -67,20 +68,20 @@ void shouldThrowExecptionOnInvalidHostAndPortString() {
6768
@Test // DATAREDIS-372
6869
void shouldThrowExceptionWhenListOfHostAndPortIsNull() {
6970
assertThatIllegalArgumentException()
70-
.isThrownBy(() -> new RedisSentinelConfiguration("mymaster", Collections.<String> singleton(null)));
71+
.isThrownBy(() -> new RedisSentinelConfiguration("mymaster", Collections.singleton(null)));
7172
}
7273

7374
@Test // DATAREDIS-372
7475
void shouldNotFailWhenListOfHostAndPortIsEmpty() {
7576

76-
RedisSentinelConfiguration config = new RedisSentinelConfiguration("mymaster", Collections.<String> emptySet());
77+
RedisSentinelConfiguration config = new RedisSentinelConfiguration("mymaster", Collections.emptySet());
7778

7879
assertThat(config.getSentinels()).isEmpty();
7980
}
8081

8182
@Test // DATAREDIS-372
8283
void shouldThrowExceptionGivenNullPropertySource() {
83-
assertThatIllegalArgumentException().isThrownBy(() -> new RedisSentinelConfiguration((PropertySource<?>) null));
84+
assertThatIllegalArgumentException().isThrownBy(() -> new RedisSentinelConfiguration(null));
8485
}
8586

8687
@Test // DATAREDIS-372
@@ -93,21 +94,22 @@ void shouldNotFailWhenGivenPropertySourceNotContainingRelevantProperties() {
9394
}
9495

9596
@Test // DATAREDIS-372
96-
void shouldBeCreatedCorrecltyGivenValidPropertySourceWithMasterAndSingleHostPort() {
97+
void shouldBeCreatedCorrectlyGivenValidPropertySourceWithMasterAndSingleHostPort() {
9798

9899
MockPropertySource propertySource = new MockPropertySource();
99100
propertySource.setProperty("spring.redis.sentinel.master", "myMaster");
100101
propertySource.setProperty("spring.redis.sentinel.nodes", HOST_AND_PORT_1);
101102

102103
RedisSentinelConfiguration config = new RedisSentinelConfiguration(propertySource);
103104

105+
assertThat(config.getMaster()).isNotNull();
104106
assertThat(config.getMaster().getName()).isEqualTo("myMaster");
105107
assertThat(config.getSentinels()).hasSize(1);
106108
assertThat(config.getSentinels()).contains(new RedisNode("127.0.0.1", 123));
107109
}
108110

109111
@Test // DATAREDIS-372
110-
void shouldBeCreatedCorrecltyGivenValidPropertySourceWithMasterAndMultipleHostPort() {
112+
void shouldBeCreatedCorrectlyGivenValidPropertySourceWithMasterAndMultipleHostPort() {
111113

112114
MockPropertySource propertySource = new MockPropertySource();
113115
propertySource.setProperty("spring.redis.sentinel.master", "myMaster");
@@ -132,6 +134,16 @@ void dataNodePasswordDoesNotAffectSentinelPassword() {
132134
assertThat(configuration.getSentinelPassword()).isEqualTo(RedisPassword.none());
133135
}
134136

137+
@Test
138+
void dataNodeUsernameDoesNotAffectSentinelUsername() {
139+
RedisSentinelConfiguration configuration = new RedisSentinelConfiguration("myMaster",
140+
Collections.singleton(HOST_AND_PORT_1));
141+
configuration.setSentinelUsername("sentinel-admin");
142+
configuration.setUsername("app");
143+
144+
assertThat(configuration.getSentinelUsername()).isEqualTo("sentinel-admin");
145+
}
146+
135147
@Test // DATAREDIS-1060
136148
void readSentinelPasswordFromConfigProperty() {
137149

@@ -145,4 +157,18 @@ void readSentinelPasswordFromConfigProperty() {
145157
assertThat(config.getSentinelPassword()).isEqualTo(RedisPassword.of("computer-says-no"));
146158
assertThat(config.getSentinels()).hasSize(1).contains(new RedisNode("127.0.0.1", 123));
147159
}
160+
161+
@Test
162+
void readSentinelUsernameFromConfigProperty() {
163+
164+
MockPropertySource propertySource = new MockPropertySource();
165+
propertySource.setProperty("spring.redis.sentinel.master", "myMaster");
166+
propertySource.setProperty("spring.redis.sentinel.nodes", HOST_AND_PORT_1);
167+
propertySource.setProperty("spring.redis.sentinel.username", "sentinel-admin");
168+
169+
RedisSentinelConfiguration config = new RedisSentinelConfiguration(propertySource);
170+
171+
assertThat(config.getSentinelUsername()).isEqualTo(RedisPassword.of("sentinel-admin"));
172+
assertThat(config.getSentinels()).hasSize(1).contains(new RedisNode("127.0.0.1", 123));
173+
}
148174
}

src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConvertersUnitTests.java

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,20 +37,23 @@
3737
import org.springframework.data.redis.connection.RedisClusterNode;
3838
import org.springframework.data.redis.connection.RedisClusterNode.Flag;
3939
import org.springframework.data.redis.connection.RedisClusterNode.LinkState;
40+
import org.springframework.data.redis.connection.RedisPassword;
41+
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
4042
import org.springframework.data.redis.connection.RedisStringCommands.SetOption;
4143
import org.springframework.data.redis.connection.RedisZSetCommands;
42-
import org.springframework.data.redis.connection.jedis.JedisConverters;
4344
import org.springframework.data.redis.core.types.Expiration;
4445
import org.springframework.data.redis.core.types.RedisClientInfo;
45-
import redis.clients.jedis.params.SetParams;
4646

4747
/**
4848
* @author Christoph Strobl
49+
* @author Vikas Garg
4950
*/
5051
class LettuceConvertersUnitTests {
5152

5253
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";
5354

55+
private static final String MASTER_NAME = "mymaster";
56+
5457
@Test // DATAREDIS-268
5558
void convertingEmptyStringToListOfRedisClientInfoShouldReturnEmptyList() {
5659
assertThat(LettuceConverters.toListOfRedisClientInformation(""))
@@ -75,12 +78,12 @@ void convertingMultipleLiesToListOfRedisClientInfoReturnsListCorrectly() {
7578
}
7679

7780
@Test // DATAREDIS-315
78-
void partitionsToClusterNodesShouldReturnEmptyCollectionWhenPartionsDoesNotContainElements() {
81+
void partitionsToClusterNodesShouldReturnEmptyCollectionWhenPartitionsDoesNotContainElements() {
7982
assertThat(LettuceConverters.partitionsToClusterNodes(new Partitions())).isNotNull();
8083
}
8184

8285
@Test // DATAREDIS-315
83-
void partitionsToClusterNodesShouldConvertPartitionCorrctly() {
86+
void partitionsToClusterNodesShouldConvertPartitionCorrectly() {
8487

8588
Partitions partitions = new Partitions();
8689

@@ -89,7 +92,7 @@ void partitionsToClusterNodesShouldConvertPartitionCorrctly() {
8992
partition.setConnected(true);
9093
partition.setFlags(new HashSet<>(Arrays.asList(NodeFlag.MASTER, NodeFlag.MYSELF)));
9194
partition.setUri(RedisURI.create("redis://" + CLUSTER_HOST + ":" + MASTER_NODE_1_PORT));
92-
partition.setSlots(Arrays.<Integer> asList(1, 2, 3, 4, 5));
95+
partition.setSlots(Arrays.asList(1, 2, 3, 4, 5));
9396

9497
partitions.add(partition);
9598

@@ -252,4 +255,58 @@ void convertsExpirationToGetExPXAT() {
252255
assertThatCommandArgument(LettuceConverters.toGetExArgs(Expiration.unixTimestamp(10, TimeUnit.MILLISECONDS)))
253256
.isEqualTo(new GetExArgs().pxAt(10));
254257
}
258+
259+
@Test
260+
void sentinelConfigurationWithAuth() {
261+
RedisPassword password = RedisPassword.of("88888888-8x8-getting-creative-now");
262+
RedisSentinelConfiguration sentinelConfiguration = new RedisSentinelConfiguration()
263+
.master(MASTER_NAME)
264+
.sentinel("127.0.0.1", 26379)
265+
.sentinel("127.0.0.1", 26380);
266+
sentinelConfiguration.setSentinelUsername("admin");
267+
sentinelConfiguration.setSentinelPassword(password);
268+
sentinelConfiguration.setUsername("app");
269+
sentinelConfiguration.setPassword(password);
270+
RedisURI redisURI = LettuceConverters.sentinelConfigurationToRedisURI(sentinelConfiguration);
271+
assertThat(redisURI.getUsername()).isEqualTo("app");
272+
redisURI.getSentinels().forEach(sentinel -> {
273+
assertThat(sentinel.getUsername()).isEqualTo("admin");
274+
});
275+
}
276+
277+
@Test
278+
void sentinelConfigurationSetSentinelPasswordIfUsernameNotPresent() {
279+
RedisPassword password = RedisPassword.of("88888888-8x8-getting-creative-now");
280+
RedisSentinelConfiguration sentinelConfiguration = new RedisSentinelConfiguration()
281+
.master(MASTER_NAME)
282+
.sentinel("127.0.0.1", 26379)
283+
.sentinel("127.0.0.1", 26380);
284+
sentinelConfiguration.setSentinelPassword(password);
285+
sentinelConfiguration.setUsername("app");
286+
sentinelConfiguration.setPassword(password);
287+
RedisURI redisURI = LettuceConverters.sentinelConfigurationToRedisURI(sentinelConfiguration);
288+
assertThat(redisURI.getUsername()).isEqualTo("app");
289+
redisURI.getSentinels().forEach(sentinel -> {
290+
assertThat(sentinel.getUsername()).isNull();
291+
assertThat(sentinel.getPassword()).isNotNull();
292+
});
293+
}
294+
295+
@Test
296+
void sentinelConfigurationShouldNotSetSentinelAuthIfUsernameIsPresentWithNoPassword() {
297+
RedisPassword password = RedisPassword.of("88888888-8x8-getting-creative-now");
298+
RedisSentinelConfiguration sentinelConfiguration = new RedisSentinelConfiguration()
299+
.master(MASTER_NAME)
300+
.sentinel("127.0.0.1", 26379)
301+
.sentinel("127.0.0.1", 26380);
302+
sentinelConfiguration.setSentinelUsername("admin");
303+
sentinelConfiguration.setUsername("app");
304+
sentinelConfiguration.setPassword(password);
305+
RedisURI redisURI = LettuceConverters.sentinelConfigurationToRedisURI(sentinelConfiguration);
306+
assertThat(redisURI.getUsername()).isEqualTo("app");
307+
redisURI.getSentinels().forEach(sentinel -> {
308+
assertThat(sentinel.getUsername()).isNull();
309+
assertThat(sentinel.getPassword()).isNull();
310+
});
311+
}
255312
}

0 commit comments

Comments
 (0)