Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for redis-sentinel in spring.redis.url (Lettuce only) #27373

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@

package org.springframework.boot.autoconfigure.data.redis;

import java.net.URI;
import java.net.URISyntaxException;
import java.time.Duration;

import io.lettuce.core.ClientOptions;
import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisURI;
import io.lettuce.core.SocketOptions;
import io.lettuce.core.TimeoutOptions;
import io.lettuce.core.cluster.ClusterClientOptions;
Expand All @@ -40,11 +43,13 @@
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.RedisSocketConfiguration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration.LettuceClientConfigurationBuilder;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

/**
Expand All @@ -58,11 +63,15 @@
@ConditionalOnProperty(name = "spring.redis.client-type", havingValue = "lettuce", matchIfMissing = true)
class LettuceConnectionConfiguration extends RedisConnectionConfiguration {

private final RedisSocketConfiguration socketConfiguration;

LettuceConnectionConfiguration(RedisProperties properties,
ObjectProvider<RedisStandaloneConfiguration> standaloneConfigurationProvider,
ObjectProvider<RedisSocketConfiguration> socketConfigurationProvider,
ObjectProvider<RedisSentinelConfiguration> sentinelConfigurationProvider,
ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider) {
super(properties, standaloneConfigurationProvider, sentinelConfigurationProvider, clusterConfigurationProvider);
this.socketConfiguration = socketConfigurationProvider.getIfAvailable();
}

@Bean(destroyMethod = "shutdown")
Expand Down Expand Up @@ -90,9 +99,72 @@ private LettuceConnectionFactory createLettuceConnectionFactory(LettuceClientCon
if (getClusterConfiguration() != null) {
return new LettuceConnectionFactory(getClusterConfiguration(), clientConfiguration);
}
if (getSocketConfiguration() != null) {
return new LettuceConnectionFactory(getSocketConfiguration(), clientConfiguration);
}
return new LettuceConnectionFactory(getStandaloneConfig(), clientConfiguration);
}

@Override
RedisSentinelConfiguration maybeGetCustomSentinelConfig() {
if (urlConfiguredForSentinelScheme(this.getProperties().getUrl())) {
RedisURI redisURI = createRedisUri(this.getProperties().getUrl());
return (RedisSentinelConfiguration) LettuceConnectionFactory.createRedisConfiguration(redisURI);
}
return null;
}

@Override
RedisStandaloneConfiguration maybeGetCustomStandaloneConfig() {
if (urlConfiguredForNonStandardStandaloneScheme(this.getProperties().getUrl())) {
RedisURI redisURI = createRedisUri(this.getProperties().getUrl());
return (RedisStandaloneConfiguration) LettuceConnectionFactory.createRedisConfiguration(redisURI);
}
return null;
}

private RedisSocketConfiguration getSocketConfiguration() {
if (this.socketConfiguration != null) {
return this.socketConfiguration;
}
if (urlConfiguredForSocketScheme(this.getProperties().getUrl())) {
RedisURI redisURI = createRedisUri(this.getProperties().getUrl());
return (RedisSocketConfiguration) LettuceConnectionFactory.createRedisConfiguration(redisURI);
}
return null;
}

private boolean urlConfiguredForSentinelScheme(String url) {
String scheme = getUrlScheme(url);
return scheme != null && RedisURI.URI_SCHEME_REDIS_SENTINEL.equals(scheme)
|| RedisURI.URI_SCHEME_REDIS_SENTINEL_SECURE.equals(scheme);
}

private boolean urlConfiguredForSocketScheme(String url) {
String scheme = getUrlScheme(url);
return scheme != null && (RedisURI.URI_SCHEME_REDIS_SOCKET.equals(scheme)
|| RedisURI.URI_SCHEME_REDIS_SOCKET_ALT.equals(scheme));
}

private boolean urlConfiguredForNonStandardStandaloneScheme(String url) {
String scheme = getUrlScheme(url);
return scheme != null && !RedisURI.URI_SCHEME_REDIS.equals(scheme)
&& !RedisURI.URI_SCHEME_REDIS_SECURE.equals(scheme);
}

private String getUrlScheme(String url) {
if (!StringUtils.hasText(url)) {
return null;
}
try {
URI uri = new URI(url);
return uri.getScheme();
}
catch (URISyntaxException ex) {
throw new RedisUrlSyntaxException(url, ex);
}
}

private LettuceClientConfiguration getLettuceClientConfiguration(
ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers,
ClientResources clientResources, Pool pool) {
Expand Down Expand Up @@ -161,10 +233,20 @@ private ClientOptions.Builder initializeClientOptionsBuilder() {
}

private void customizeConfigurationFromUrl(LettuceClientConfiguration.LettuceClientConfigurationBuilder builder) {
ConnectionInfo connectionInfo = parseUrl(getProperties().getUrl());
if (connectionInfo.isUseSsl()) {
builder.useSsl();
RedisURI redisURI = createRedisUri(getProperties().getUrl());
builder.apply(redisURI);
}

private RedisURI createRedisUri(String uri) {
RedisURI redisURI = RedisURI.create(uri);
// Set the sentinel password from properties on the sentinel nodes as there is no
// way to set that on the url
if (!ObjectUtils.isEmpty(redisURI.getSentinels()) && getProperties().getSentinel() != null
&& StringUtils.hasText(getProperties().getSentinel().getPassword())) {
redisURI.getSentinels().forEach((sentinelNodeRedisUri) -> sentinelNodeRedisUri
.setPassword(getProperties().getSentinel().getPassword().toCharArray()));
}
return redisURI;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ protected final RedisStandaloneConfiguration getStandaloneConfig() {
if (this.standaloneConfiguration != null) {
return this.standaloneConfiguration;
}

RedisStandaloneConfiguration customConfig = maybeGetCustomStandaloneConfig();
if (customConfig != null) {
return customConfig;
}

RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
if (StringUtils.hasText(this.properties.getUrl())) {
ConnectionInfo connectionInfo = parseUrl(this.properties.getUrl());
Expand All @@ -86,10 +92,20 @@ protected final RedisStandaloneConfiguration getStandaloneConfig() {
return config;
}

RedisStandaloneConfiguration maybeGetCustomStandaloneConfig() {
return null;
}

protected final RedisSentinelConfiguration getSentinelConfig() {
if (this.sentinelConfiguration != null) {
return this.sentinelConfiguration;
}

RedisSentinelConfiguration customConfig = maybeGetCustomSentinelConfig();
if (customConfig != null) {
return customConfig;
}

RedisProperties.Sentinel sentinelProperties = this.properties.getSentinel();
if (sentinelProperties != null) {
RedisSentinelConfiguration config = new RedisSentinelConfiguration();
Expand All @@ -108,6 +124,10 @@ protected final RedisSentinelConfiguration getSentinelConfig() {
return null;
}

RedisSentinelConfiguration maybeGetCustomSentinelConfig() {
return null;
}

/**
* Create a {@link RedisClusterConfiguration} if necessary.
* @return {@literal null} if no cluster settings are set.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,15 @@
import org.springframework.test.util.ReflectionTestUtils;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;

/**
* Tests for {@link RedisAutoConfiguration} when Lettuce is not on the classpath.
*
* @author Mark Paluch
* @author Stephane Nicoll
* @author Weix Sun
* @author Chris Bono
*/
@ClassPathExclusions("lettuce-core-*.jar")
class RedisAutoConfigurationJedisTests {
Expand Down Expand Up @@ -209,6 +211,17 @@ void testRedisConfigurationWithSentinelAndAuthentication() {
});
}

@Test
void testRedisSentinelUrlConfiguration() {
this.contextRunner
.withPropertyValues(
"spring.redis.url=redis-sentinel://username:[email protected]:26379,127.0.0.1:26380/mymaster")
.run((context) -> assertThatIllegalStateException()
.isThrownBy(() -> context.getBean(JedisConnectionFactory.class))
.withRootCauseInstanceOf(RedisUrlSyntaxException.class).havingRootCause().withMessageContaining(
"Invalid Redis URL 'redis-sentinel://username:[email protected]:26379,127.0.0.1:26380/mymaster'"));
}

@Test
void testRedisConfigurationWithCluster() {
this.contextRunner.withPropertyValues("spring.redis.cluster.nodes=127.0.0.1:27379,127.0.0.1:27380")
Expand Down
Loading