|
1 | 1 | /*
|
2 |
| - * Copyright 2012-2024 the original author or authors. |
| 2 | + * Copyright 2012-2025 the original author or authors. |
3 | 3 | *
|
4 | 4 | * Licensed under the Apache License, Version 2.0 (the "License");
|
5 | 5 | * you may not use this file except in compliance with the License.
|
|
19 | 19 | import java.time.Duration;
|
20 | 20 | import java.util.Arrays;
|
21 | 21 | import java.util.EnumSet;
|
| 22 | +import java.util.Iterator; |
22 | 23 | import java.util.List;
|
23 | 24 | import java.util.Set;
|
24 | 25 | import java.util.function.Consumer;
|
25 | 26 | import java.util.stream.Collectors;
|
| 27 | +import java.util.stream.Stream; |
26 | 28 |
|
27 | 29 | import io.lettuce.core.ClientOptions;
|
| 30 | +import io.lettuce.core.ReadFrom; |
| 31 | +import io.lettuce.core.ReadFrom.Nodes; |
| 32 | +import io.lettuce.core.RedisURI; |
28 | 33 | import io.lettuce.core.cluster.ClusterClientOptions;
|
29 | 34 | import io.lettuce.core.cluster.ClusterTopologyRefreshOptions.RefreshTrigger;
|
| 35 | +import io.lettuce.core.cluster.models.partitions.RedisClusterNode; |
| 36 | +import io.lettuce.core.models.role.RedisNodeDescription; |
30 | 37 | import io.lettuce.core.resource.DefaultClientResources;
|
31 | 38 | import io.lettuce.core.tracing.Tracing;
|
32 | 39 | import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
|
33 | 40 | import org.junit.jupiter.api.Test;
|
34 | 41 | import org.junit.jupiter.api.condition.EnabledForJreRange;
|
35 | 42 | import org.junit.jupiter.api.condition.JRE;
|
| 43 | +import org.junit.jupiter.params.ParameterizedTest; |
| 44 | +import org.junit.jupiter.params.provider.Arguments; |
| 45 | +import org.junit.jupiter.params.provider.MethodSource; |
36 | 46 |
|
37 | 47 | import org.springframework.boot.autoconfigure.AutoConfigurations;
|
38 | 48 | import org.springframework.boot.autoconfigure.data.redis.RedisProperties.Pool;
|
@@ -112,6 +122,60 @@ void testOverrideRedisConfiguration() {
|
112 | 122 | });
|
113 | 123 | }
|
114 | 124 |
|
| 125 | + @ParameterizedTest(name = "{0}") |
| 126 | + @MethodSource |
| 127 | + void shouldConfigureLettuceReadFromProperty(String type, ReadFrom readFrom) { |
| 128 | + this.contextRunner.withPropertyValues("spring.data.redis.lettuce.read-from:" + type).run((context) -> { |
| 129 | + LettuceConnectionFactory factory = context.getBean(LettuceConnectionFactory.class); |
| 130 | + LettuceClientConfiguration configuration = factory.getClientConfiguration(); |
| 131 | + assertThat(configuration.getReadFrom()).hasValue(readFrom); |
| 132 | + }); |
| 133 | + } |
| 134 | + |
| 135 | + static Stream<Arguments> shouldConfigureLettuceReadFromProperty() { |
| 136 | + return Stream.of(Arguments.of("any", ReadFrom.ANY), Arguments.of("any-replica", ReadFrom.ANY_REPLICA), |
| 137 | + Arguments.of("lowest-latency", ReadFrom.LOWEST_LATENCY), Arguments.of("replica", ReadFrom.REPLICA), |
| 138 | + Arguments.of("replica-preferred", ReadFrom.REPLICA_PREFERRED), |
| 139 | + Arguments.of("upstream", ReadFrom.UPSTREAM), |
| 140 | + Arguments.of("upstream-preferred", ReadFrom.UPSTREAM_PREFERRED)); |
| 141 | + } |
| 142 | + |
| 143 | + @Test |
| 144 | + void shouldConfigureLettuceRegexReadFromProperty() { |
| 145 | + RedisClusterNode node1 = createRedisNode("redis-node-1.region-1.example.com"); |
| 146 | + RedisClusterNode node2 = createRedisNode("redis-node-2.region-1.example.com"); |
| 147 | + RedisClusterNode node3 = createRedisNode("redis-node-1.region-2.example.com"); |
| 148 | + RedisClusterNode node4 = createRedisNode("redis-node-2.region-2.example.com"); |
| 149 | + this.contextRunner.withPropertyValues("spring.data.redis.lettuce.read-from:regex:.*region-1.*") |
| 150 | + .run((context) -> { |
| 151 | + LettuceConnectionFactory factory = context.getBean(LettuceConnectionFactory.class); |
| 152 | + LettuceClientConfiguration configuration = factory.getClientConfiguration(); |
| 153 | + assertThat(configuration.getReadFrom()).hasValueSatisfying((readFrom) -> { |
| 154 | + List<RedisNodeDescription> result = readFrom.select(new RedisNodes(node1, node2, node3, node4)); |
| 155 | + assertThat(result).hasSize(2).containsExactly(node1, node2); |
| 156 | + }); |
| 157 | + }); |
| 158 | + } |
| 159 | + |
| 160 | + @Test |
| 161 | + void shouldConfigureLettuceSubnetReadFromProperty() { |
| 162 | + RedisClusterNode nodeInSubnetIpv4 = createRedisNode("192.0.2.1"); |
| 163 | + RedisClusterNode nodeNotInSubnetIpv4 = createRedisNode("198.51.100.1"); |
| 164 | + RedisClusterNode nodeInSubnetIpv6 = createRedisNode("2001:db8:abcd:0000::1"); |
| 165 | + RedisClusterNode nodeNotInSubnetIpv6 = createRedisNode("2001:db8:abcd:1000::"); |
| 166 | + this.contextRunner |
| 167 | + .withPropertyValues("spring.data.redis.lettuce.read-from:subnet:192.0.2.0/24,2001:db8:abcd:0000::/52") |
| 168 | + .run((context) -> { |
| 169 | + LettuceConnectionFactory factory = context.getBean(LettuceConnectionFactory.class); |
| 170 | + LettuceClientConfiguration configuration = factory.getClientConfiguration(); |
| 171 | + assertThat(configuration.getReadFrom()).hasValueSatisfying((readFrom) -> { |
| 172 | + List<RedisNodeDescription> result = readFrom.select(new RedisNodes(nodeInSubnetIpv4, |
| 173 | + nodeNotInSubnetIpv4, nodeInSubnetIpv6, nodeNotInSubnetIpv6)); |
| 174 | + assertThat(result).hasSize(2).containsExactly(nodeInSubnetIpv4, nodeInSubnetIpv6); |
| 175 | + }); |
| 176 | + }); |
| 177 | + } |
| 178 | + |
115 | 179 | @Test
|
116 | 180 | void testCustomizeClientResources() {
|
117 | 181 | Tracing tracing = mock(Tracing.class);
|
@@ -632,6 +696,32 @@ private String getUserName(LettuceConnectionFactory factory) {
|
632 | 696 | return ReflectionTestUtils.invokeMethod(factory, "getRedisUsername");
|
633 | 697 | }
|
634 | 698 |
|
| 699 | + private RedisClusterNode createRedisNode(String host) { |
| 700 | + RedisClusterNode node = new RedisClusterNode(); |
| 701 | + node.setUri(RedisURI.Builder.redis(host).build()); |
| 702 | + return node; |
| 703 | + } |
| 704 | + |
| 705 | + private static final class RedisNodes implements Nodes { |
| 706 | + |
| 707 | + private final List<RedisNodeDescription> descriptions; |
| 708 | + |
| 709 | + RedisNodes(RedisNodeDescription... descriptions) { |
| 710 | + this.descriptions = List.of(descriptions); |
| 711 | + } |
| 712 | + |
| 713 | + @Override |
| 714 | + public List<RedisNodeDescription> getNodes() { |
| 715 | + return this.descriptions; |
| 716 | + } |
| 717 | + |
| 718 | + @Override |
| 719 | + public Iterator<RedisNodeDescription> iterator() { |
| 720 | + return this.descriptions.iterator(); |
| 721 | + } |
| 722 | + |
| 723 | + } |
| 724 | + |
635 | 725 | @Configuration(proxyBeanMethods = false)
|
636 | 726 | static class CustomConfiguration {
|
637 | 727 |
|
|
0 commit comments