DATAREDIS-1046 - Polishing.

Guard tests to allow execution against single node instance for quick dev turnaround and replace usage of Optional with null and @Nullable annotations.

Original Pull Request: #558
This commit is contained in:
Christoph Strobl
2020-09-11 11:49:12 +02:00
parent 717a196da0
commit bdbba085da
13 changed files with 168 additions and 69 deletions

View File

@@ -23,7 +23,6 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.springframework.core.env.MapPropertySource;
@@ -50,7 +49,7 @@ public class RedisClusterConfiguration implements RedisConfiguration, ClusterCon
private Set<RedisNode> clusterNodes;
private @Nullable Integer maxRedirects;
private Optional<String> username = Optional.empty();
private @Nullable String username = null;
private RedisPassword password = RedisPassword.none();
/**
@@ -189,16 +188,17 @@ public class RedisClusterConfiguration implements RedisConfiguration, ClusterCon
* @see org.springframework.data.redis.connection.RedisConfiguration.WithAuthentication#setUsername(String)
*/
@Override
public void setUsername(String username) {
this.username = Optional.of(username);
public void setUsername(@Nullable String username) {
this.username = username;
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisConfiguration.WithAuthentication#getUsername()
*/
@Nullable
@Override
public Optional<String> getUsername() {
public String getUsername() {
return this.username;
}

View File

@@ -17,7 +17,6 @@ package org.springframework.data.redis.connection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.IntSupplier;
import java.util.function.Supplier;
@@ -138,11 +137,11 @@ public interface RedisConfiguration {
* @param configuration can be {@literal null}.
* @param other a {@code Supplier} whose result is returned if given {@link RedisConfiguration} is not
* {@link #isAuthenticationAware(RedisConfiguration) password aware}.
* @return never {@literal null}.
* @return can be {@literal null}.
* @throws IllegalArgumentException if {@code other} is {@literal null}.
*/
static Optional<String> getUsernameOrElse(@Nullable RedisConfiguration configuration,
Supplier<Optional<String>> other) {
@Nullable
static String getUsernameOrElse(@Nullable RedisConfiguration configuration, Supplier<String> other) {
Assert.notNull(other, "Other must not be null!");
return isAuthenticationAware(configuration) ? ((WithAuthentication) configuration).getUsername() : other.get();
@@ -203,7 +202,7 @@ public interface RedisConfiguration {
*
* @param username the username.
*/
void setUsername(String username);
void setUsername(@Nullable String username);
/**
* Create and set a {@link RedisPassword} for given {@link String}.
@@ -233,9 +232,10 @@ public interface RedisConfiguration {
/**
* Get the username to use when connecting.
*
* @return {@link Optional#empty()} if none set.
* @return {@literal null} if none set.
*/
Optional<String> getUsername();
@Nullable
String getUsername();
/**
* Get the RedisPassword to use when connecting.
@@ -381,10 +381,11 @@ public interface RedisConfiguration {
/**
* Get the username used when authenticating with a Redis Server.
*
* @return never {@literal null}.
* @return can be {@literal null} if not set.
* @since 2.4
*/
default Optional<String> getDataNodeUsername() {
@Nullable
default String getDataNodeUsername() {
return getUsername();
}

View File

@@ -21,7 +21,6 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.springframework.core.env.MapPropertySource;
@@ -51,7 +50,7 @@ public class RedisSentinelConfiguration implements RedisConfiguration, SentinelC
private Set<RedisNode> sentinels;
private int database;
private Optional<String> dataNodeUsername = Optional.empty();
private @Nullable String dataNodeUsername = null;
private RedisPassword dataNodePassword = RedisPassword.none();
private RedisPassword sentinelPassword = RedisPassword.none();
@@ -236,16 +235,17 @@ public class RedisSentinelConfiguration implements RedisConfiguration, SentinelC
* @see org.springframework.data.redis.connection.RedisConfiguration.WithAuthentication#setUsername(String)
*/
@Override
public void setUsername(String username) {
this.dataNodeUsername = Optional.of(username);
public void setUsername(@Nullable String username) {
this.dataNodeUsername = username;
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisConfiguration.WithAuthentication#getUsername()
*/
@Nullable
@Override
public Optional<String> getUsername() {
public String getUsername() {
return this.dataNodeUsername;
}

View File

@@ -15,9 +15,8 @@
*/
package org.springframework.data.redis.connection;
import java.util.Optional;
import org.springframework.data.redis.connection.RedisConfiguration.DomainSocketConfiguration;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
@@ -34,7 +33,7 @@ public class RedisSocketConfiguration implements RedisConfiguration, DomainSocke
private String socket = DEFAULT_SOCKET;
private int database;
private Optional<String> username = Optional.empty();
private @Nullable String username = null;
private RedisPassword password = RedisPassword.none();
/**
@@ -100,16 +99,17 @@ public class RedisSocketConfiguration implements RedisConfiguration, DomainSocke
* @see org.springframework.data.redis.connection.RedisConfiguration.WithAuthentication#setUsername(String)
*/
@Override
public void setUsername(String username) {
this.username = Optional.of(username);
public void setUsername(@Nullable String username) {
this.username = username;
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisConfiguration.WithAuthentication#getUsername()
*/
@Nullable
@Override
public Optional<String> getUsername() {
public String getUsername() {
return this.username;
}

View File

@@ -15,11 +15,10 @@
*/
package org.springframework.data.redis.connection;
import java.util.Optional;
import org.springframework.data.redis.connection.RedisConfiguration.WithDatabaseIndex;
import org.springframework.data.redis.connection.RedisConfiguration.WithHostAndPort;
import org.springframework.data.redis.connection.RedisConfiguration.WithPassword;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
@@ -39,7 +38,7 @@ public class RedisStandaloneConfiguration
private String hostName = DEFAULT_HOST;
private int port = DEFAULT_PORT;
private int database;
private Optional<String> username = Optional.empty();
private @Nullable String username = null;
private RedisPassword password = RedisPassword.none();
/**
@@ -133,16 +132,17 @@ public class RedisStandaloneConfiguration
* @see org.springframework.data.redis.connection.RedisConfiguration.WithAuthentication#setUsername(String)
*/
@Override
public void setUsername(String username) {
this.username = Optional.of(username);
public void setUsername(@Nullable String username) {
this.username = username;
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisConfiguration.WithAuthentication#getUsername()
*/
@Nullable
@Override
public Optional<String> getUsername() {
public String getUsername() {
return this.username;
}

View File

@@ -18,17 +18,17 @@ package org.springframework.data.redis.connection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.springframework.data.redis.connection.RedisConfiguration.StaticMasterReplicaConfiguration;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Configuration class used for setting up {@link RedisConnection} via {@link RedisConnectionFactory} using the provided
* Master / Replica configuration to nodes know to not change address. Eg. when connecting to
* <a href="https://aws.amazon.com/documentation/elasticache/">AWS ElastiCache with Read Replicas</a>. <br/>
* Note: Redis is undergoing a nomenclature change where the term replica is used synonymously to slave.
* Please also note that a Master/Replica connection cannot be used for Pub/Sub operations.
* Note: Redis is undergoing a nomenclature change where the term replica is used synonymously to slave. Please also
* note that a Master/Replica connection cannot be used for Pub/Sub operations.
*
* @author Mark Paluch
* @author Christoph Strobl
@@ -41,7 +41,7 @@ public class RedisStaticMasterReplicaConfiguration implements RedisConfiguration
private List<RedisStandaloneConfiguration> nodes = new ArrayList<>();
private int database;
private Optional<String> username = Optional.empty();
private @Nullable String username = null;
private RedisPassword password = RedisPassword.none();
/**
@@ -137,16 +137,17 @@ public class RedisStaticMasterReplicaConfiguration implements RedisConfiguration
* @see org.springframework.data.redis.connection.RedisConfiguration.WithAuthentication#setUsername(String)
*/
@Override
public void setUsername(String username) {
this.username = Optional.of(username);
public void setUsername(@Nullable String username) {
this.username = username;
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisConfiguration.WithAuthentication#getUsername()
*/
@Nullable
@Override
public Optional<String> getUsername() {
public String getUsername() {
return this.username;
}

View File

@@ -58,6 +58,7 @@ import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
/**
* Connection factory creating <a href="https://github.com/xetorthio/jedis">Jedis</a> based connections.
@@ -328,7 +329,10 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean,
clientConfiguration.getHostnameVerifier().orElse(null));
getRedisPassword().map(String::new).ifPresent(shardInfo::setPassword);
getRedisUsername().ifPresent(shardInfo::setUser);
String username = getRedisUsername();
if (StringUtils.hasText(username)) {
shardInfo.setUser(username);
}
int readTimeout = getReadTimeout();
@@ -375,8 +379,8 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean,
String sentinelPassword = config.getSentinelPassword().toOptional().map(String::new).orElse(null);
return new JedisSentinelPool(config.getMaster().getName(), convertToJedisSentinelSet(config.getSentinels()),
poolConfig, getConnectTimeout(), getReadTimeout(), getUsername(), getPassword(), getDatabase(),
getClientName(), getConnectTimeout(), getReadTimeout(), sentinelUser, sentinelPassword, getClientName());
poolConfig, getConnectTimeout(), getReadTimeout(), getUsername(), getPassword(), getDatabase(), getClientName(),
getConnectTimeout(), getReadTimeout(), sentinelUser, sentinelPassword, getClientName());
}
/**
@@ -556,7 +560,7 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean,
*/
@Nullable
private String getUsername() {
return getRedisUsername().orElse(null);
return getRedisUsername();
}
/**
@@ -569,7 +573,8 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean,
return getRedisPassword().map(String::new).orElse(null);
}
private Optional<String> getRedisUsername() {
@Nullable
private String getRedisUsername() {
return RedisConfiguration.getUsernameOrElse(this.configuration, standaloneConfig::getUsername);
}

View File

@@ -44,7 +44,6 @@ import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.dao.DataAccessException;
@@ -62,6 +61,7 @@ import org.springframework.data.util.Optionals;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* Connection factory creating <a href="https://github.com/mp911de/lettuce">Lettuce</a>-based connections.
@@ -788,9 +788,11 @@ public class LettuceConnectionFactory
this.getMutableConfiguration().setClientName(clientName);
}
private Optional<String> getRedisUsername() {
@Nullable
private String getRedisUsername() {
return RedisConfiguration.getUsernameOrElse(configuration, standaloneConfig::getUsername);
}
/**
* Returns the password used for authenticating with the Redis server.
*
@@ -1164,11 +1166,10 @@ public class LettuceConnectionFactory
private void applyAuthentication(RedisURI.Builder builder) {
Optional<String> username = getRedisUsername();
if (username.isPresent()) {
String username = getRedisUsername();
if (StringUtils.hasText(username)) {
// See https://github.com/lettuce-io/lettuce-core/issues/1404
username.ifPresent(
it -> builder.withAuthentication(it, new String(getRedisPassword().toOptional().orElse(new char[0]))));
builder.withAuthentication(username, new String(getRedisPassword().toOptional().orElse(new char[0])));
} else {
getRedisPassword().toOptional().ifPresent(builder::withPassword);
}

View File

@@ -309,7 +309,6 @@ abstract public class LettuceConverters extends Converters {
TRANSACTION_RESULT_UNWRAPPER = transactionResult -> transactionResult.stream().collect(Collectors.toList());
}
public static List<Tuple> toTuple(List<byte[]> list) {
@@ -653,12 +652,12 @@ abstract public class LettuceConverters extends Converters {
builder.withSentinel(sentinelBuilder.build());
}
Optional<String> username = sentinelConfiguration.getUsername();
String username = sentinelConfiguration.getUsername();
RedisPassword password = sentinelConfiguration.getPassword();
if (username.isPresent()) {
if (StringUtils.hasText(username)) {
// See https://github.com/lettuce-io/lettuce-core/issues/1404
builder.withAuthentication(username.get(), new String(password.toOptional().orElse(new char[0])));
builder.withAuthentication(username, new String(password.toOptional().orElse(new char[0])));
} else {
password.toOptional().ifPresent(builder::withPassword);
}

View File

@@ -16,31 +16,32 @@
package org.springframework.data.redis.connection.jedis;
import static org.assertj.core.api.Assertions.*;
import static org.junit.Assume.*;
import java.io.IOException;
import java.util.Collections;
import org.junit.Before;
import org.junit.Assume;
import org.junit.ClassRule;
import org.junit.Test;
import org.springframework.data.redis.RedisTestProfileValueSource;
import org.junit.rules.RuleChain;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.RedisSentinelConnection;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.test.util.MinimumRedisVersionRule;
import org.springframework.data.redis.test.util.ServerAvailable;
import org.springframework.test.annotation.IfProfileValue;
/**
* Integration tests for Redis 6 ACL.
*
* @author Mark Paluch
*/
@IfProfileValue(name = "redisVersion", value = "6.0+")
public class JedisAclIntegrationTests {
@Before
public void before() {
assumeTrue(RedisTestProfileValueSource.atLeast("redisVersion", "6.0"));
}
@ClassRule public static RuleChain requirements = RuleChain.outerRule(ServerAvailable.runningAtLocalhost(6382))
.around(new MinimumRedisVersionRule());
@Test
public void shouldConnectWithDefaultAuthentication() {
@@ -80,6 +81,9 @@ public class JedisAclIntegrationTests {
@Test // DATAREDIS-1145
public void shouldConnectSentinelWithAclAuthentication() throws IOException {
Assume.assumeTrue("Redis Sentinel at localhost:26382 did not answer.",
ServerAvailable.runningAtLocalhost(26382).isAvailable());
// Note: As per https://github.com/redis/redis/issues/7708, Sentinel does not support ACL authentication yet.
RedisSentinelConfiguration sentinelConfiguration = new RedisSentinelConfiguration("mymaster",

View File

@@ -22,7 +22,9 @@ import io.lettuce.core.pubsub.StatefulRedisPubSubConnection;
import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.springframework.data.redis.test.util.ServerAvailable;
/**
* Integration test of {@link AuthenticatingRedisClient}.
@@ -35,6 +37,8 @@ public class AuthenticatingRedisClientTests {
private RedisClient client;
@ClassRule public static ServerAvailable serverAvailable = ServerAvailable.runningAtLocalhost(6382);
@Before
public void setUp() {
client = new AuthenticatingRedisClient("localhost", 6382, "foobared");

View File

@@ -16,7 +16,6 @@
package org.springframework.data.redis.connection.lettuce;
import static org.assertj.core.api.Assertions.*;
import static org.junit.Assume.*;
import io.lettuce.core.ClientOptions;
import io.lettuce.core.protocol.ProtocolVersion;
@@ -24,28 +23,29 @@ import io.lettuce.core.protocol.ProtocolVersion;
import java.io.IOException;
import java.util.Collections;
import org.junit.Before;
import org.junit.Assume;
import org.junit.ClassRule;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.data.redis.RedisTestProfileValueSource;
import org.junit.rules.RuleChain;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.RedisSentinelConnection;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.RedisStaticMasterReplicaConfiguration;
import org.springframework.data.redis.test.util.MinimumRedisVersionRule;
import org.springframework.data.redis.test.util.ServerAvailable;
/**
* Integration tests for Redis 6 ACL.
*
* @author Mark Paluch
* @author Christoph Strobl
*/
public class LettuceAclIntegrationTests {
@Before
public void before() {
assumeTrue(RedisTestProfileValueSource.atLeast("redisVersion", "6.0"));
}
@ClassRule public static RuleChain requirements = RuleChain.outerRule(ServerAvailable.runningAtLocalhost(6382))
.around(new MinimumRedisVersionRule());
@Test // DATAREDIS-1046
public void shouldConnectWithDefaultAuthentication() {
@@ -87,6 +87,9 @@ public class LettuceAclIntegrationTests {
@Test // DATAREDIS-1145
public void shouldConnectSentinelWithAuthentication() throws IOException {
Assume.assumeTrue("Redis Sentinel at localhost:26382 did not answer.",
ServerAvailable.runningAtLocalhost(26382).isAvailable());
// Note: As per https://github.com/redis/redis/issues/7708, Sentinel does not support ACL authentication yet.
LettuceClientConfiguration configuration = LettuceClientConfiguration.builder()

View File

@@ -0,0 +1,81 @@
/*
* Copyright 2020 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
*
* https://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.net.InetSocketAddress;
import java.net.Socket;
import org.junit.AssumptionViolatedException;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
/**
* @author Christoph Strobl
*/
public class ServerAvailable implements TestRule {
private final String host;
private final int port;
ServerAvailable(String host, int port) {
this.host = host;
this.port = port;
}
public static ServerAvailable runningAtLocalhost() {
return runningAtLocalhost(6379);
}
public static ServerAvailable runningAtLocalhost(int port) {
return runningAt("localhost", port);
}
public static ServerAvailable runningAt(String host, int port) {
return new ServerAvailable(host, port);
}
@Override
public Statement apply(Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
if (!isAvailable()) {
throw new AssumptionViolatedException(
String.format("Redis Server (%s:%s) did not answer. Is it up and running?", host, port));
}
base.evaluate();
}
};
}
public boolean isAvailable() {
try (Socket socket = new Socket()) {
socket.connect(new InetSocketAddress(host, port), 100);
return true;
} catch (Exception e) {
return false;
}
}
}