From fd91f4a0ccda4babe8c5ba261f4147b48921de1c Mon Sep 17 00:00:00 2001 From: Sorokin Evgeniy Date: Tue, 15 Aug 2023 23:54:27 +0300 Subject: [PATCH] Add support for IPv6 hostnames to Converters. Closes #2678 --- .../redis/connection/convert/Converters.java | 18 ++++++--- .../convert/ConvertersUnitTests.java | 39 +++++++++++++++++++ 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/springframework/data/redis/connection/convert/Converters.java b/src/main/java/org/springframework/data/redis/connection/convert/Converters.java index cd7f1203d9..78be1b3e38 100644 --- a/src/main/java/org/springframework/data/redis/connection/convert/Converters.java +++ b/src/main/java/org/springframework/data/redis/connection/convert/Converters.java @@ -60,6 +60,7 @@ * @author Christoph Strobl * @author daihuabin * @author John Blum + * @author Sorokin Evgeniy */ public abstract class Converters { @@ -560,21 +561,28 @@ enum ClusterNodesConverter implements Converter { public RedisClusterNode convert(String source) { String[] args = source.split(" "); - String[] hostAndPort = StringUtils.split(args[HOST_PORT_INDEX], ":"); - Assert.notNull(hostAndPort, "ClusterNode information does not define host and port"); + int lastColonIndex = args[HOST_PORT_INDEX].lastIndexOf(":"); + + Assert.isTrue(lastColonIndex >= 0 && lastColonIndex < args[HOST_PORT_INDEX].length() - 1, + "ClusterNode information does not define host and port"); + + String portPart = args[HOST_PORT_INDEX].substring(lastColonIndex + 1); + String hostPart = args[HOST_PORT_INDEX].substring(0, lastColonIndex); SlotRange range = parseSlotRange(args); Set flags = parseFlags(args); - String portPart = hostAndPort[1]; - if (portPart.contains("@")) { portPart = portPart.substring(0, portPart.indexOf('@')); } + if (hostPart.startsWith("[") && hostPart.endsWith("]")) { + hostPart = hostPart.substring(1, hostPart.length() - 1); + } + RedisClusterNodeBuilder nodeBuilder = RedisClusterNode.newRedisClusterNode() - .listeningAt(hostAndPort[0], Integer.parseInt(portPart)) // + .listeningAt(hostPart, Integer.parseInt(portPart)) // .withId(args[ID_INDEX]) // .promotedAs(flags.contains(Flag.MASTER) ? NodeType.MASTER : NodeType.REPLICA) // .serving(range) // diff --git a/src/test/java/org/springframework/data/redis/connection/convert/ConvertersUnitTests.java b/src/test/java/org/springframework/data/redis/connection/convert/ConvertersUnitTests.java index 1e8fe36120..d183d5f687 100644 --- a/src/test/java/org/springframework/data/redis/connection/convert/ConvertersUnitTests.java +++ b/src/test/java/org/springframework/data/redis/connection/convert/ConvertersUnitTests.java @@ -31,6 +31,7 @@ * * @author Christoph Strobl * @author Mark Paluch + * @author Sorokin Evgeniy */ class ConvertersUnitTests { @@ -60,6 +61,12 @@ class ConvertersUnitTests { private static final String CLUSTER_NODE_WITHOUT_HOST = "ef570f86c7b1a953846668debc177a3a16733420 :6379 fail,master - 0 0 1 connected"; + private static final String CLUSTER_NODE_WITH_SINGLE_IPV6_HOST = "67adfe3df1058896e3cb49d2863e0f70e7e159fa 2a02:6b8:c67:9c:0:6d8b:33da:5a2c:6380@16380,redis-master master,nofailover - 0 1692108412315 1 connected 0-5460"; + + private static final String CLUSTER_NODE_WITH_SINGLE_IPV6_HOST_SQUARE_BRACKETS = "67adfe3df1058896e3cb49d2863e0f70e7e159fa [2a02:6b8:c67:9c:0:6d8b:33da:5a2c]:6380@16380,redis-master master,nofailover - 0 1692108412315 1 connected 0-5460"; + + private static final String CLUSTER_NODE_WITH_SINGLE_INVALID_IPV6_HOST = "67adfe3df1058896e3cb49d2863e0f70e7e159fa 2a02:6b8:c67:9c:0:6d8b:33da:5a2c: master,nofailover - 0 1692108412315 1 connected 0-5460"; + @Test // DATAREDIS-315 void toSetOfRedis30ClusterNodesShouldConvertSingleStringNodesResponseCorrectly() { @@ -222,4 +229,36 @@ void toSetOfRedisClusterNodesShouldAllowEmptyHostname() { assertThat(node.getSlotRange().getSlots().size()).isEqualTo(0); } + @Test // https://github.com/spring-projects/spring-data-redis/issues/2678 + void toClusterNodeWithIPv6Hostname() { + RedisClusterNode node = Converters.toClusterNode(CLUSTER_NODE_WITH_SINGLE_IPV6_HOST); + + assertThat(node.getId()).isEqualTo("67adfe3df1058896e3cb49d2863e0f70e7e159fa"); + assertThat(node.getHost()).isEqualTo("2a02:6b8:c67:9c:0:6d8b:33da:5a2c"); + assertThat(node.hasValidHost()).isTrue(); + assertThat(node.getPort()).isEqualTo(6380); + assertThat(node.getType()).isEqualTo(NodeType.MASTER); + assertThat(node.getFlags()).contains(Flag.MASTER); + assertThat(node.getLinkState()).isEqualTo(LinkState.CONNECTED); + assertThat(node.getSlotRange().getSlots().size()).isEqualTo(5461); + } + + @Test // https://github.com/spring-projects/spring-data-redis/issues/2678 + void toClusterNodeWithIPv6HostnameSquareBrackets() { + RedisClusterNode node = Converters.toClusterNode(CLUSTER_NODE_WITH_SINGLE_IPV6_HOST_SQUARE_BRACKETS); + + assertThat(node.getId()).isEqualTo("67adfe3df1058896e3cb49d2863e0f70e7e159fa"); + assertThat(node.getHost()).isEqualTo("2a02:6b8:c67:9c:0:6d8b:33da:5a2c"); + assertThat(node.hasValidHost()).isTrue(); + assertThat(node.getPort()).isEqualTo(6380); + assertThat(node.getType()).isEqualTo(NodeType.MASTER); + assertThat(node.getFlags()).contains(Flag.MASTER); + assertThat(node.getLinkState()).isEqualTo(LinkState.CONNECTED); + assertThat(node.getSlotRange().getSlots().size()).isEqualTo(5461); + } + + @Test // https://github.com/spring-projects/spring-data-redis/issues/2678 + void toClusterNodeWithInvalidIPv6Hostname() { + assertThatIllegalArgumentException().isThrownBy(() -> Converters.toClusterNode(CLUSTER_NODE_WITH_SINGLE_INVALID_IPV6_HOST)); + } }