Skip to content

Commit a956ba8

Browse files
mp911dechristophstrobl
authored andcommitted
Accept cluster nodes without hostname.
RedisNode can now be constructed using an empty hostname. This can happen when a node is in failover state. RedisNode exposes hasValidHost() to check whether the node has a valid hostname. Also, introduce copy constructor to avoid mutations caused by the builder. Original Pull Request: #1991
1 parent ff0cd5a commit a956ba8

File tree

5 files changed

+68
-4
lines changed

5 files changed

+68
-4
lines changed

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -197,11 +197,11 @@ public RedisClusterNode lookup(RedisClusterNode node) {
197197

198198
Assert.notNull(node, "RedisClusterNode must not be null!");
199199

200-
if (nodes.contains(node) && StringUtils.hasText(node.getHost()) && StringUtils.hasText(node.getId())) {
200+
if (nodes.contains(node) && node.hasValidHost() && StringUtils.hasText(node.getId())) {
201201
return node;
202202
}
203203

204-
if (StringUtils.hasText(node.getHost()) && node.getPort() != null) {
204+
if (node.hasValidHost() && node.getPort() != null) {
205205
return lookup(node.getHost(), node.getPort());
206206
}
207207

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

+21-2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import org.springframework.lang.Nullable;
1919
import org.springframework.util.Assert;
2020
import org.springframework.util.ObjectUtils;
21+
import org.springframework.util.StringUtils;
2122

2223
/**
2324
* @author Christoph Strobl
@@ -50,6 +51,16 @@ public RedisNode(String host, int port) {
5051

5152
protected RedisNode() {}
5253

54+
private RedisNode(RedisNode redisNode) {
55+
56+
this.id = redisNode.id;
57+
this.name = redisNode.name;
58+
this.host = redisNode.host;
59+
this.port = redisNode.port;
60+
this.type = redisNode.type;
61+
this.masterId = redisNode.masterId;
62+
}
63+
5364
/**
5465
* @return can be {@literal null}.
5566
*/
@@ -58,6 +69,14 @@ public String getHost() {
5869
return host;
5970
}
6071

72+
/**
73+
* @return whether this node has a valid host (not null and not empty).
74+
* @since 2.3.8
75+
*/
76+
public boolean hasValidHost() {
77+
return StringUtils.hasText(host);
78+
}
79+
6180
/**
6281
* @return can be {@literal null}.
6382
*/
@@ -229,7 +248,7 @@ public RedisNodeBuilder withName(String name) {
229248
*/
230249
public RedisNodeBuilder listeningAt(String host, int port) {
231250

232-
Assert.hasText(host, "Hostname must not be empty or null.");
251+
Assert.notNull(host, "Hostname must not be null.");
233252
node.host = host;
234253
node.port = port;
235254
return this;
@@ -290,7 +309,7 @@ public RedisNodeBuilder replicaOf(String masterId) {
290309
* @return
291310
*/
292311
public RedisNode build() {
293-
return this.node;
312+
return new RedisNode(this.node);
294313
}
295314
}
296315

src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterConnection.java

+5
Original file line numberDiff line numberDiff line change
@@ -951,6 +951,11 @@ private Jedis getConnectionForSpecificNode(RedisClusterNode node) {
951951

952952
RedisClusterNode member = topologyProvider.getTopology().lookup(node);
953953

954+
if (!member.hasValidHost()) {
955+
throw new DataAccessResourceFailureException(String
956+
.format("Cannot obtain connection to node %ss as it is not associated with a hostname!", node.getId()));
957+
}
958+
954959
if (member != null && connectionHandler != null) {
955960
return connectionHandler.getConnectionFromNode(new HostAndPort(member.getHost(), member.getPort()));
956961
}

src/test/java/org/springframework/data/redis/connection/convert/ConvertersUnitTests.java

+21
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import org.springframework.data.redis.connection.RedisNode.NodeType;
2828

2929
/**
30+
* Unit tests for {@link Converters}.
31+
*
3032
* @author Christoph Strobl
3133
* @author Mark Paluch
3234
*/
@@ -56,6 +58,8 @@ public class ConvertersUnitTests {
5658

5759
private static final String CLUSTER_NODE_IMPORTING_SLOT = "ef570f86c7b1a953846668debc177a3a16733420 127.0.0.1:6379 myself,master - 0 0 1 connected [5461-<-0f2ee5df45d18c50aca07228cc18b1da96fd5e84]";
5860

61+
private static final String CLUSTER_NODE_WITHOUT_HOST = "ef570f86c7b1a953846668debc177a3a16733420 :6379 fail,master - 0 0 1 connected";
62+
5963
@Test // DATAREDIS-315
6064
public void toSetOfRedis30ClusterNodesShouldConvertSingleStringNodesResponseCorrectly() {
6165

@@ -194,6 +198,23 @@ public void toSetOfRedisClusterNodesShouldIgnoreImportingSlot() {
194198
RedisClusterNode node = nodes.next();
195199
assertThat(node.getId()).isEqualTo("ef570f86c7b1a953846668debc177a3a16733420");
196200
assertThat(node.getHost()).isEqualTo("127.0.0.1");
201+
assertThat(node.hasValidHost()).isTrue();
202+
assertThat(node.getPort()).isEqualTo(6379);
203+
assertThat(node.getType()).isEqualTo(NodeType.MASTER);
204+
assertThat(node.getFlags()).contains(Flag.MASTER);
205+
assertThat(node.getLinkState()).isEqualTo(LinkState.CONNECTED);
206+
assertThat(node.getSlotRange().getSlots().size()).isEqualTo(0);
207+
}
208+
209+
@Test // GH-1985
210+
public void toSetOfRedisClusterNodesShouldAllowEmptyHostname() {
211+
212+
Iterator<RedisClusterNode> nodes = Converters.toSetOfRedisClusterNodes(CLUSTER_NODE_WITHOUT_HOST).iterator();
213+
214+
RedisClusterNode node = nodes.next();
215+
assertThat(node.getId()).isEqualTo("ef570f86c7b1a953846668debc177a3a16733420");
216+
assertThat(node.getHost()).isEmpty();
217+
assertThat(node.hasValidHost()).isFalse();
197218
assertThat(node.getPort()).isEqualTo(6379);
198219
assertThat(node.getType()).isEqualTo(NodeType.MASTER);
199220
assertThat(node.getFlags()).contains(Flag.MASTER);

src/test/java/org/springframework/data/redis/connection/jedis/JedisClusterConnectionUnitTests.java

+19
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,25 @@ public void clusterTopologyProviderShouldRequestTopology() {
393393
verify(con1Mock, times(2)).clusterNodes();
394394
}
395395

396+
@Test // GH-1985
397+
public void nodeWithoutHostShouldRejectConnectionAttempt() {
398+
399+
reset(con1Mock, con2Mock, con3Mock);
400+
401+
when(con1Mock.clusterNodes())
402+
.thenReturn("ef570f86c7b1a953846668debc177a3a16733420 :6379 fail,master - 0 0 1 connected");
403+
when(con2Mock.clusterNodes())
404+
.thenReturn("ef570f86c7b1a953846668debc177a3a16733420 :6379 fail,master - 0 0 1 connected");
405+
when(con3Mock.clusterNodes())
406+
.thenReturn("ef570f86c7b1a953846668debc177a3a16733420 :6379 fail,master - 0 0 1 connected");
407+
408+
JedisClusterConnection connection = new JedisClusterConnection(clusterMock);
409+
410+
assertThatThrownBy(() -> connection.ping(new RedisClusterNode("ef570f86c7b1a953846668debc177a3a16733420")))
411+
.isInstanceOf(DataAccessResourceFailureException.class)
412+
.hasMessageContaining("ef570f86c7b1a953846668debc177a3a16733420");
413+
}
414+
396415
static class StubJedisCluster extends JedisCluster {
397416

398417
JedisClusterConnectionHandler connectionHandler;

0 commit comments

Comments
 (0)