diff --git a/pom.xml b/pom.xml index 81102e8008..2da7b4eb0a 100644 --- a/pom.xml +++ b/pom.xml @@ -50,7 +50,7 @@ 1.7.36 1.7.1 2.17.2 - 3.3.1 + 3.5.0 @@ -89,7 +89,7 @@ org.locationtech.jts jts-core - 1.19.0 + 1.20.0 test @@ -139,7 +139,7 @@ org.apache.httpcomponents.client5 httpclient5-fluent - 5.3.1 + 5.4 test @@ -240,7 +240,7 @@ maven-javadoc-plugin - 3.8.0 + 3.10.0 8 false @@ -315,7 +315,7 @@ maven-gpg-plugin - 3.2.5 + 3.2.6 --pinentry-mode diff --git a/src/main/java/redis/clients/jedis/JedisCluster.java b/src/main/java/redis/clients/jedis/JedisCluster.java index 0b9f5b49d1..db8d17ee15 100644 --- a/src/main/java/redis/clients/jedis/JedisCluster.java +++ b/src/main/java/redis/clients/jedis/JedisCluster.java @@ -23,16 +23,38 @@ public class JedisCluster extends UnifiedJedis { * Default timeout in milliseconds. */ public static final int DEFAULT_TIMEOUT = 2000; + + /** + * Default amount of attempts for executing a command + */ public static final int DEFAULT_MAX_ATTEMPTS = 5; + /** + * Creates a JedisCluster instance. The provided node is used to make the first contact with the cluster.
+ * Here, the default timeout of {@value JedisCluster#DEFAULT_TIMEOUT} ms is being used with {@value JedisCluster#DEFAULT_MAX_ATTEMPTS} maximum attempts. + * @param node Node to first connect to. + */ public JedisCluster(HostAndPort node) { this(Collections.singleton(node)); } + /** + * Creates a JedisCluster instance. The provided node is used to make the first contact with the cluster.
+ * Here, the default timeout of {@value JedisCluster#DEFAULT_TIMEOUT} ms is being used with {@value JedisCluster#DEFAULT_MAX_ATTEMPTS} maximum attempts. + * @param node Node to first connect to. + * @param timeout connection and socket timeout in milliseconds. + */ public JedisCluster(HostAndPort node, int timeout) { this(Collections.singleton(node), timeout); } + /** + * Creates a JedisCluster instance. The provided node is used to make the first contact with the cluster.
+ * You can specify the timeout and the maximum attempts. + * @param node Node to first connect to. + * @param timeout connection and socket timeout in milliseconds. + * @param maxAttempts maximum attempts for executing a command. + */ public JedisCluster(HostAndPort node, int timeout, int maxAttempts) { this(Collections.singleton(node), timeout, maxAttempts); } @@ -93,14 +115,32 @@ public JedisCluster(HostAndPort node, final JedisClientConfig clientConfig, int this(Collections.singleton(node), clientConfig, maxAttempts, poolConfig); } + /** + * Creates a JedisCluster with multiple entry points. + * Here, the default timeout of {@value JedisCluster#DEFAULT_TIMEOUT} ms is being used with {@value JedisCluster#DEFAULT_MAX_ATTEMPTS} maximum attempts. + * @param nodes Nodes to connect to. + */ public JedisCluster(Set nodes) { this(nodes, DEFAULT_TIMEOUT); } + /** + * Creates a JedisCluster with multiple entry points. + * Here, the default timeout of {@value JedisCluster#DEFAULT_TIMEOUT} ms is being used with {@value JedisCluster#DEFAULT_MAX_ATTEMPTS} maximum attempts. + * @param nodes Nodes to connect to. + * @param timeout connection and socket timeout in milliseconds. + */ public JedisCluster(Set nodes, int timeout) { this(nodes, DefaultJedisClientConfig.builder().timeoutMillis(timeout).build()); } + /** + * Creates a JedisCluster with multiple entry points.
+ * You can specify the timeout and the maximum attempts. + * @param nodes Nodes to connect to. + * @param timeout connection and socket timeout in milliseconds. + * @param maxAttempts maximum attempts for executing a command. + */ public JedisCluster(Set nodes, int timeout, int maxAttempts) { this(nodes, DefaultJedisClientConfig.builder().timeoutMillis(timeout).build(), maxAttempts); } @@ -186,6 +226,19 @@ public JedisCluster(Set clusterNodes, JedisClientConfig clientConfi Duration.ofMillis((long) clientConfig.getSocketTimeoutMillis() * maxAttempts)); } + /** + * Creates a JedisCluster with multiple entry points.
+ * You can specify the timeout and the maximum attempts.
+ * + * Additionally, you are free to provide a {@link JedisClientConfig} instance.
+ * You can use the {@link DefaultJedisClientConfig#builder()} builder pattern to customize your configuration, including socket timeouts, + * username and passwords as well as SSL related parameters. + * + * @param clusterNodes Nodes to connect to. + * @param clientConfig Client configuration parameters. + * @param maxAttempts maximum attempts for executing a command. + * @param maxTotalRetriesDuration Maximum time used for reconnecting. + */ public JedisCluster(Set clusterNodes, JedisClientConfig clientConfig, int maxAttempts, Duration maxTotalRetriesDuration) { this(new ClusterConnectionProvider(clusterNodes, clientConfig), maxAttempts, maxTotalRetriesDuration, @@ -216,6 +269,11 @@ public JedisCluster(Set clusterNodes, JedisClientConfig clientConfi maxAttempts, maxTotalRetriesDuration, clientConfig.getRedisProtocol()); } + // Uses a fetched connection to process protocol. Should be avoided if possible. + public JedisCluster(ClusterConnectionProvider provider, int maxAttempts, Duration maxTotalRetriesDuration) { + super(provider, maxAttempts, maxTotalRetriesDuration); + } + private JedisCluster(ClusterConnectionProvider provider, int maxAttempts, Duration maxTotalRetriesDuration, RedisProtocol protocol) { super(provider, maxAttempts, maxTotalRetriesDuration, protocol); @@ -268,15 +326,20 @@ private JedisCluster(ClusterConnectionProvider provider, int maxAttempts, Durati super(provider, maxAttempts, maxTotalRetriesDuration, protocol, clientSideCache); } - // Uses a fetched connection to process protocol. Should be avoided if possible. - public JedisCluster(ClusterConnectionProvider provider, int maxAttempts, Duration maxTotalRetriesDuration) { - super(provider, maxAttempts, maxTotalRetriesDuration); - } - + /** + * Returns all nodes that were configured to connect to in key-value pairs ({@link Map}).
+ * Key is the HOST:PORT and the value is the connection pool. + * @return the map of all connections. + */ public Map getClusterNodes() { return ((ClusterConnectionProvider) provider).getNodes(); } + /** + * Returns the connection for one of the 16,384 slots. + * @param slot the slot to retrieve the connection for. + * @return connection of the provided slot. {@code close()} of this connection must be called after use. + */ public Connection getConnectionFromSlot(int slot) { return ((ClusterConnectionProvider) provider).getConnectionFromSlot(slot); } diff --git a/src/main/java/redis/clients/jedis/providers/MultiClusterPooledConnectionProvider.java b/src/main/java/redis/clients/jedis/providers/MultiClusterPooledConnectionProvider.java index eb443bca1e..097bf636e0 100644 --- a/src/main/java/redis/clients/jedis/providers/MultiClusterPooledConnectionProvider.java +++ b/src/main/java/redis/clients/jedis/providers/MultiClusterPooledConnectionProvider.java @@ -24,6 +24,7 @@ import redis.clients.jedis.*; import redis.clients.jedis.MultiClusterClientConfig.ClusterConfig; import redis.clients.jedis.annots.Experimental; +import redis.clients.jedis.annots.VisibleForTesting; import redis.clients.jedis.exceptions.JedisConnectionException; import redis.clients.jedis.exceptions.JedisValidationException; import redis.clients.jedis.util.Pool; @@ -299,6 +300,11 @@ public Cluster getCluster() { return multiClusterMap.get(activeMultiClusterIndex); } + @VisibleForTesting + public Cluster getCluster(int multiClusterIndex) { + return multiClusterMap.get(multiClusterIndex); + } + public CircuitBreaker getClusterCircuitBreaker() { return multiClusterMap.get(activeMultiClusterIndex).getCircuitBreaker(); } diff --git a/src/main/java/redis/clients/jedis/timeseries/TSAddParams.java b/src/main/java/redis/clients/jedis/timeseries/TSAddParams.java index 0a9713cefb..487b5301a2 100644 --- a/src/main/java/redis/clients/jedis/timeseries/TSAddParams.java +++ b/src/main/java/redis/clients/jedis/timeseries/TSAddParams.java @@ -5,6 +5,7 @@ import java.util.LinkedHashMap; import java.util.Map; +import java.util.Objects; import redis.clients.jedis.CommandArguments; import redis.clients.jedis.params.IParams; @@ -125,4 +126,36 @@ public void addParams(CommandArguments args) { labels.entrySet().forEach((entry) -> args.add(entry.getKey()).add(entry.getValue())); } } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + TSAddParams that = (TSAddParams) o; + return ignore == that.ignore && ignoreMaxTimediff == that.ignoreMaxTimediff && + Double.compare(ignoreMaxValDiff, that.ignoreMaxValDiff) == 0 && + Objects.equals(retentionPeriod, that.retentionPeriod) && + encoding == that.encoding && Objects.equals(chunkSize, that.chunkSize) && + duplicatePolicy == that.duplicatePolicy && onDuplicate == that.onDuplicate && + Objects.equals(labels, that.labels); + } + + @Override + public int hashCode() { + int result = Objects.hashCode(retentionPeriod); + result = 31 * result + Objects.hashCode(encoding); + result = 31 * result + Objects.hashCode(chunkSize); + result = 31 * result + Objects.hashCode(duplicatePolicy); + result = 31 * result + Objects.hashCode(onDuplicate); + result = 31 * result + Boolean.hashCode(ignore); + result = 31 * result + Long.hashCode(ignoreMaxTimediff); + result = 31 * result + Double.hashCode(ignoreMaxValDiff); + result = 31 * result + Objects.hashCode(labels); + return result; + } } diff --git a/src/main/java/redis/clients/jedis/timeseries/TSAlterParams.java b/src/main/java/redis/clients/jedis/timeseries/TSAlterParams.java index 50ba9723ac..2a65cc74d9 100644 --- a/src/main/java/redis/clients/jedis/timeseries/TSAlterParams.java +++ b/src/main/java/redis/clients/jedis/timeseries/TSAlterParams.java @@ -6,6 +6,7 @@ import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Objects; import redis.clients.jedis.CommandArguments; import redis.clients.jedis.params.IParams; @@ -106,4 +107,33 @@ public void addParams(CommandArguments args) { labels.entrySet().forEach((entry) -> args.add(entry.getKey()).add(entry.getValue())); } } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + TSAlterParams that = (TSAlterParams) o; + return ignore == that.ignore && ignoreMaxTimediff == that.ignoreMaxTimediff && + Double.compare(ignoreMaxValDiff, that.ignoreMaxValDiff) == 0 && + Objects.equals(retentionPeriod, that.retentionPeriod) && + Objects.equals(chunkSize, that.chunkSize) && + duplicatePolicy == that.duplicatePolicy && Objects.equals(labels, that.labels); + } + + @Override + public int hashCode() { + int result = Objects.hashCode(retentionPeriod); + result = 31 * result + Objects.hashCode(chunkSize); + result = 31 * result + Objects.hashCode(duplicatePolicy); + result = 31 * result + Boolean.hashCode(ignore); + result = 31 * result + Long.hashCode(ignoreMaxTimediff); + result = 31 * result + Double.hashCode(ignoreMaxValDiff); + result = 31 * result + Objects.hashCode(labels); + return result; + } } diff --git a/src/main/java/redis/clients/jedis/timeseries/TSArithByParams.java b/src/main/java/redis/clients/jedis/timeseries/TSArithByParams.java index 1bc3df1c55..7703816d25 100644 --- a/src/main/java/redis/clients/jedis/timeseries/TSArithByParams.java +++ b/src/main/java/redis/clients/jedis/timeseries/TSArithByParams.java @@ -5,6 +5,7 @@ import java.util.LinkedHashMap; import java.util.Map; +import java.util.Objects; import redis.clients.jedis.CommandArguments; import redis.clients.jedis.params.IParams; @@ -117,4 +118,36 @@ public void addParams(CommandArguments args) { labels.entrySet().forEach((entry) -> args.add(entry.getKey()).add(entry.getValue())); } } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + TSArithByParams that = (TSArithByParams) o; + return ignore == that.ignore && ignoreMaxTimediff == that.ignoreMaxTimediff && + Double.compare(ignoreMaxValDiff, that.ignoreMaxValDiff) == 0 && + Objects.equals(timestamp, that.timestamp) && + Objects.equals(retentionPeriod, that.retentionPeriod) && + encoding == that.encoding && Objects.equals(chunkSize, that.chunkSize) && + duplicatePolicy == that.duplicatePolicy && Objects.equals(labels, that.labels); + } + + @Override + public int hashCode() { + int result = Objects.hashCode(timestamp); + result = 31 * result + Objects.hashCode(retentionPeriod); + result = 31 * result + Objects.hashCode(encoding); + result = 31 * result + Objects.hashCode(chunkSize); + result = 31 * result + Objects.hashCode(duplicatePolicy); + result = 31 * result + Boolean.hashCode(ignore); + result = 31 * result + Long.hashCode(ignoreMaxTimediff); + result = 31 * result + Double.hashCode(ignoreMaxValDiff); + result = 31 * result + Objects.hashCode(labels); + return result; + } } diff --git a/src/main/java/redis/clients/jedis/timeseries/TSCreateParams.java b/src/main/java/redis/clients/jedis/timeseries/TSCreateParams.java index 0611383d4d..c9c5face72 100644 --- a/src/main/java/redis/clients/jedis/timeseries/TSCreateParams.java +++ b/src/main/java/redis/clients/jedis/timeseries/TSCreateParams.java @@ -5,6 +5,7 @@ import java.util.LinkedHashMap; import java.util.Map; +import java.util.Objects; import redis.clients.jedis.CommandArguments; import redis.clients.jedis.params.IParams; @@ -121,4 +122,34 @@ public void addParams(CommandArguments args) { labels.entrySet().forEach((entry) -> args.add(entry.getKey()).add(entry.getValue())); } } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + TSCreateParams that = (TSCreateParams) o; + return ignore == that.ignore && ignoreMaxTimediff == that.ignoreMaxTimediff && + Double.compare(ignoreMaxValDiff, that.ignoreMaxValDiff) == 0 && + Objects.equals(retentionPeriod, that.retentionPeriod) && + encoding == that.encoding && Objects.equals(chunkSize, that.chunkSize) && + duplicatePolicy == that.duplicatePolicy && Objects.equals(labels, that.labels); + } + + @Override + public int hashCode() { + int result = Objects.hashCode(retentionPeriod); + result = 31 * result + Objects.hashCode(encoding); + result = 31 * result + Objects.hashCode(chunkSize); + result = 31 * result + Objects.hashCode(duplicatePolicy); + result = 31 * result + Boolean.hashCode(ignore); + result = 31 * result + Long.hashCode(ignoreMaxTimediff); + result = 31 * result + Double.hashCode(ignoreMaxValDiff); + result = 31 * result + Objects.hashCode(labels); + return result; + } } diff --git a/src/main/java/redis/clients/jedis/timeseries/TSGetParams.java b/src/main/java/redis/clients/jedis/timeseries/TSGetParams.java index eb35cf1eae..3525b0e18f 100644 --- a/src/main/java/redis/clients/jedis/timeseries/TSGetParams.java +++ b/src/main/java/redis/clients/jedis/timeseries/TSGetParams.java @@ -27,4 +27,22 @@ public void addParams(CommandArguments args) { args.add(LATEST); } } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + TSGetParams that = (TSGetParams) o; + return latest == that.latest; + } + + @Override + public int hashCode() { + return Boolean.hashCode(latest); + } } diff --git a/src/main/java/redis/clients/jedis/timeseries/TSMGetParams.java b/src/main/java/redis/clients/jedis/timeseries/TSMGetParams.java index c7d08ffde2..c4272c04c3 100644 --- a/src/main/java/redis/clients/jedis/timeseries/TSMGetParams.java +++ b/src/main/java/redis/clients/jedis/timeseries/TSMGetParams.java @@ -4,6 +4,7 @@ import static redis.clients.jedis.timeseries.TimeSeriesProtocol.TimeSeriesKeyword.SELECTED_LABELS; import static redis.clients.jedis.timeseries.TimeSeriesProtocol.TimeSeriesKeyword.WITHLABELS; +import java.util.Arrays; import redis.clients.jedis.CommandArguments; import redis.clients.jedis.params.IParams; @@ -55,4 +56,26 @@ public void addParams(CommandArguments args) { } } } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + TSMGetParams that = (TSMGetParams) o; + return latest == that.latest && withLabels == that.withLabels && + Arrays.equals(selectedLabels, that.selectedLabels); + } + + @Override + public int hashCode() { + int result = Boolean.hashCode(latest); + result = 31 * result + Boolean.hashCode(withLabels); + result = 31 * result + Arrays.hashCode(selectedLabels); + return result; + } } diff --git a/src/main/java/redis/clients/jedis/timeseries/TSMRangeParams.java b/src/main/java/redis/clients/jedis/timeseries/TSMRangeParams.java index 182c90814b..cafe091a3a 100644 --- a/src/main/java/redis/clients/jedis/timeseries/TSMRangeParams.java +++ b/src/main/java/redis/clients/jedis/timeseries/TSMRangeParams.java @@ -7,6 +7,8 @@ import static redis.clients.jedis.timeseries.TimeSeriesProtocol.TimeSeriesKeyword.*; import static redis.clients.jedis.util.SafeEncoder.encode; +import java.util.Arrays; +import java.util.Objects; import redis.clients.jedis.CommandArguments; import redis.clients.jedis.params.IParams; @@ -260,4 +262,50 @@ public void addParams(CommandArguments args) { args.add(GROUPBY).add(groupByLabel).add(REDUCE).add(groupByReduce); } } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + TSMRangeParams that = (TSMRangeParams) o; + return latest == that.latest && withLabels == that.withLabels && + bucketDuration == that.bucketDuration && empty == that.empty && + Objects.equals(fromTimestamp, that.fromTimestamp) && + Objects.equals(toTimestamp, that.toTimestamp) && + Arrays.equals(filterByTimestamps, that.filterByTimestamps) && + Arrays.equals(filterByValues, that.filterByValues) && + Arrays.equals(selectedLabels, that.selectedLabels) && + Objects.equals(count, that.count) && Arrays.equals(align, that.align) && + aggregationType == that.aggregationType && + Arrays.equals(bucketTimestamp, that.bucketTimestamp) && + Arrays.equals(filters, that.filters) && + Objects.equals(groupByLabel, that.groupByLabel) && + Objects.equals(groupByReduce, that.groupByReduce); + } + + @Override + public int hashCode() { + int result = Objects.hashCode(fromTimestamp); + result = 31 * result + Objects.hashCode(toTimestamp); + result = 31 * result + Boolean.hashCode(latest); + result = 31 * result + Arrays.hashCode(filterByTimestamps); + result = 31 * result + Arrays.hashCode(filterByValues); + result = 31 * result + Boolean.hashCode(withLabels); + result = 31 * result + Arrays.hashCode(selectedLabels); + result = 31 * result + Objects.hashCode(count); + result = 31 * result + Arrays.hashCode(align); + result = 31 * result + Objects.hashCode(aggregationType); + result = 31 * result + Long.hashCode(bucketDuration); + result = 31 * result + Arrays.hashCode(bucketTimestamp); + result = 31 * result + Boolean.hashCode(empty); + result = 31 * result + Arrays.hashCode(filters); + result = 31 * result + Objects.hashCode(groupByLabel); + result = 31 * result + Objects.hashCode(groupByReduce); + return result; + } } diff --git a/src/main/java/redis/clients/jedis/timeseries/TSRangeParams.java b/src/main/java/redis/clients/jedis/timeseries/TSRangeParams.java index bab70dcbd6..402e2b88e6 100644 --- a/src/main/java/redis/clients/jedis/timeseries/TSRangeParams.java +++ b/src/main/java/redis/clients/jedis/timeseries/TSRangeParams.java @@ -7,6 +7,8 @@ import static redis.clients.jedis.timeseries.TimeSeriesProtocol.TimeSeriesKeyword.*; import static redis.clients.jedis.util.SafeEncoder.encode; +import java.util.Arrays; +import java.util.Objects; import redis.clients.jedis.CommandArguments; import redis.clients.jedis.params.IParams; @@ -205,4 +207,40 @@ public void addParams(CommandArguments args) { } } } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + TSRangeParams that = (TSRangeParams) o; + return latest == that.latest && bucketDuration == that.bucketDuration && empty == that.empty && + Objects.equals(fromTimestamp, that.fromTimestamp) && + Objects.equals(toTimestamp, that.toTimestamp) && + Arrays.equals(filterByTimestamps, that.filterByTimestamps) && + Arrays.equals(filterByValues, that.filterByValues) && + Objects.equals(count, that.count) && Arrays.equals(align, that.align) && + aggregationType == that.aggregationType && + Arrays.equals(bucketTimestamp, that.bucketTimestamp); + } + + @Override + public int hashCode() { + int result = Objects.hashCode(fromTimestamp); + result = 31 * result + Objects.hashCode(toTimestamp); + result = 31 * result + Boolean.hashCode(latest); + result = 31 * result + Arrays.hashCode(filterByTimestamps); + result = 31 * result + Arrays.hashCode(filterByValues); + result = 31 * result + Objects.hashCode(count); + result = 31 * result + Arrays.hashCode(align); + result = 31 * result + Objects.hashCode(aggregationType); + result = 31 * result + Long.hashCode(bucketDuration); + result = 31 * result + Arrays.hashCode(bucketTimestamp); + result = 31 * result + Boolean.hashCode(empty); + return result; + } } diff --git a/src/test/java/io/redis/examples/CmdsGenericExample.java b/src/test/java/io/redis/examples/CmdsGenericExample.java new file mode 100644 index 0000000000..c43364f105 --- /dev/null +++ b/src/test/java/io/redis/examples/CmdsGenericExample.java @@ -0,0 +1,113 @@ +// EXAMPLE: cmds_generic +// REMOVE_START +package io.redis.examples; + +import org.junit.Assert; +import org.junit.Test; + +// REMOVE_END +// HIDE_START +import redis.clients.jedis.UnifiedJedis; +import redis.clients.jedis.args.ExpiryOption; +// HIDE_END + +// HIDE_START +public class CmdsGenericExample { + @Test + public void run() { + UnifiedJedis jedis = new UnifiedJedis("redis://localhost:6379"); + + //REMOVE_START + // Clear any keys here before using them in tests. + + //REMOVE_END +// HIDE_END + + // STEP_START del + String delResult1 = jedis.set("key1", "Hello"); + System.out.println(delResult1); // >>> OK + + String delResult2 = jedis.set("key2", "World"); + System.out.println(delResult2); // >>> OK + + long delResult3 = jedis.del("key1", "key2", "key3"); + System.out.println(delResult3); // >>> 2 + // STEP_END + + // Tests for 'del' step. + // REMOVE_START + Assert.assertEquals("OK", delResult1); + Assert.assertEquals("OK", delResult2); + Assert.assertEquals(2, delResult3); + // REMOVE_END + + + // STEP_START expire + String expireResult1 = jedis.set("mykey", "Hello"); + System.out.println(expireResult1); // >>> OK + + long expireResult2 = jedis.expire("mykey", 10); + System.out.println(expireResult2); // >>> 1 + + long expireResult3 = jedis.ttl("mykey"); + System.out.println(expireResult3); // >>> 10 + + String expireResult4 = jedis.set("mykey", "Hello World"); + System.out.println(expireResult4); // >>> OK + + long expireResult5 = jedis.ttl("mykey"); + System.out.println(expireResult5); // >>> -1 + + long expireResult6 = jedis.expire("mykey", 10, ExpiryOption.XX); + System.out.println(expireResult6); // >>> 0 + + long expireResult7 = jedis.ttl("mykey"); + System.out.println(expireResult7); // >>> -1 + + long expireResult8 = jedis.expire("mykey", 10, ExpiryOption.NX); + System.out.println(expireResult8); // >>> 1 + + long expireResult9 = jedis.ttl("mykey"); + System.out.println(expireResult9); // >>> 10 + // STEP_END + + // Tests for 'expire' step. + // REMOVE_START + Assert.assertEquals("OK", expireResult1); + Assert.assertEquals(1, expireResult2); + Assert.assertEquals(10, expireResult3); + Assert.assertEquals("OK", expireResult4); + Assert.assertEquals(-1, expireResult5); + Assert.assertEquals(0, expireResult6); + Assert.assertEquals(-1, expireResult7); + Assert.assertEquals(1, expireResult8); + Assert.assertEquals(10, expireResult9); + jedis.del("mykey"); + // REMOVE_END + + + // STEP_START ttl + String ttlResult1 = jedis.set("mykey", "Hello"); + System.out.println(ttlResult1); // >>> OK + + long ttlResult2 = jedis.expire("mykey", 10); + System.out.println(ttlResult2); // >>> 1 + + long ttlResult3 = jedis.ttl("mykey"); + System.out.println(ttlResult3); // >>> 10 + // STEP_END + + // Tests for 'ttl' step. + // REMOVE_START + Assert.assertEquals("OK", ttlResult1); + Assert.assertEquals(1, ttlResult2); + Assert.assertEquals(10, ttlResult3); + jedis.del("mykey"); + // REMOVE_END + +// HIDE_START + + } +} +// HIDE_END + diff --git a/src/test/java/io/redis/examples/CmdsSortedSetExample.java b/src/test/java/io/redis/examples/CmdsSortedSetExample.java new file mode 100644 index 0000000000..19a5c21b8e --- /dev/null +++ b/src/test/java/io/redis/examples/CmdsSortedSetExample.java @@ -0,0 +1,480 @@ +// EXAMPLE: cmds_sorted_set +// REMOVE_START +package io.redis.examples; + +import org.junit.Assert; +import org.junit.Test; + +// REMOVE_END +// HIDE_START +// HIDE_END +import java.util.HashMap; +import java.util.Map; +import java.util.List; +import redis.clients.jedis.UnifiedJedis; +import redis.clients.jedis.params.ZRangeParams; +import redis.clients.jedis.resps.Tuple; + +// HIDE_START +public class CmdsSortedSetExample { + @Test + public void run() { + UnifiedJedis jedis = new UnifiedJedis("redis://localhost:6379"); + + //REMOVE_START + // Clear any keys here before using them in tests. + jedis.del("myzset"); + //REMOVE_END +// HIDE_END + + + // STEP_START bzmpop + + // STEP_END + + // Tests for 'bzmpop' step. + // REMOVE_START + + // REMOVE_END + + + // STEP_START bzpopmax + + // STEP_END + + // Tests for 'bzpopmax' step. + // REMOVE_START + + // REMOVE_END + + + // STEP_START bzpopmin + + // STEP_END + + // Tests for 'bzpopmin' step. + // REMOVE_START + + // REMOVE_END + + + // STEP_START zadd + Map zAddExampleParams = new HashMap<>(); + zAddExampleParams.put("one", 1.0); + long zAddResult1 = jedis.zadd("myzset", zAddExampleParams); + System.out.println(zAddResult1); // >>> 1 + + zAddExampleParams.clear(); + zAddExampleParams.put("uno", 1.0); + long zAddResult2 = jedis.zadd("myzset", zAddExampleParams); + System.out.println(zAddResult2); // >>> 1 + + zAddExampleParams.clear(); + zAddExampleParams.put("two", 2.0); + zAddExampleParams.put("three", 3.0); + long zAddResult3 = jedis.zadd("myzset", zAddExampleParams); + System.out.println(zAddResult3); // >>> 2 + + List zAddResult4 = jedis.zrangeWithScores("myzset", new ZRangeParams(0, -1)); + + for (Tuple item: zAddResult4) { + System.out.println("Element: " + item.getElement() + ", Score: " + item.getScore()); + } + // >>> Element: one, Score: 1.0 + // >>> Element: uno, Score: 1.0 + // >>> Element: two, Score: 2.0 + // >>> Element: three, Score: 3.0 + // STEP_END + + // Tests for 'zadd' step. + // REMOVE_START + Assert.assertEquals(1, zAddResult1); + Assert.assertEquals(1, zAddResult2); + Assert.assertEquals(2, zAddResult3); + Assert.assertEquals(new Tuple("one", 1.0), zAddResult4.get(0)); + Assert.assertEquals(new Tuple("uno", 1.0), zAddResult4.get(1)); + Assert.assertEquals(new Tuple("two", 2.0), zAddResult4.get(2)); + Assert.assertEquals(new Tuple("three", 3.0), zAddResult4.get(3)); + jedis.del("myzset"); + // REMOVE_END + + + // STEP_START zcard + + // STEP_END + + // Tests for 'zcard' step. + // REMOVE_START + + // REMOVE_END + + + // STEP_START zcount + + // STEP_END + + // Tests for 'zcount' step. + // REMOVE_START + + // REMOVE_END + + + // STEP_START zdiff + + // STEP_END + + // Tests for 'zdiff' step. + // REMOVE_START + + // REMOVE_END + + + // STEP_START zdiffstore + + // STEP_END + + // Tests for 'zdiffstore' step. + // REMOVE_START + + // REMOVE_END + + + // STEP_START zincrby + + // STEP_END + + // Tests for 'zincrby' step. + // REMOVE_START + + // REMOVE_END + + + // STEP_START zinter + + // STEP_END + + // Tests for 'zinter' step. + // REMOVE_START + + // REMOVE_END + + + // STEP_START zintercard + + // STEP_END + + // Tests for 'zintercard' step. + // REMOVE_START + + // REMOVE_END + + + // STEP_START zinterstore + + // STEP_END + + // Tests for 'zinterstore' step. + // REMOVE_START + + // REMOVE_END + + + // STEP_START zlexcount + + // STEP_END + + // Tests for 'zlexcount' step. + // REMOVE_START + + // REMOVE_END + + + // STEP_START zmpop + + // STEP_END + + // Tests for 'zmpop' step. + // REMOVE_START + + // REMOVE_END + + + // STEP_START zmscore + + // STEP_END + + // Tests for 'zmscore' step. + // REMOVE_START + + // REMOVE_END + + + // STEP_START zpopmax + + // STEP_END + + // Tests for 'zpopmax' step. + // REMOVE_START + + // REMOVE_END + + + // STEP_START zpopmin + + // STEP_END + + // Tests for 'zpopmin' step. + // REMOVE_START + + // REMOVE_END + + + // STEP_START zrandmember + + // STEP_END + + // Tests for 'zrandmember' step. + // REMOVE_START + + // REMOVE_END + + + // STEP_START zrange1 + Map zRangeExampleParams1 = new HashMap<>(); + zRangeExampleParams1.put("one", 1.0); + zRangeExampleParams1.put("two", 2.0); + zRangeExampleParams1.put("three", 3.0); + long zRangeResult1 = jedis.zadd("myzset", zRangeExampleParams1); + System.out.println(zRangeResult1); // >>> 3 + + List zRangeResult2 = jedis.zrange("myzset", new ZRangeParams(0, -1)); + System.out.println(String.join(", ", zRangeResult2)); // >>> one, two, three + + List zRangeResult3 = jedis.zrange("myzset", new ZRangeParams(2, 3)); + System.out.println(String.join(", ", zRangeResult3)); // >> three + + List zRangeResult4 = jedis.zrange("myzset", new ZRangeParams(-2, -1)); + System.out.println(String.join(", ", zRangeResult4)); // >> two, three + // STEP_END + + // Tests for 'zrange1' step. + // REMOVE_START + Assert.assertEquals(3, zRangeResult1); + Assert.assertEquals("one, two, three", String.join(", ", zRangeResult2)); + Assert.assertEquals("three", String.join(", ", zRangeResult3)); + Assert.assertEquals("two, three", String.join(", ", zRangeResult4)); + jedis.del("myzset"); + // REMOVE_END + + + // STEP_START zrange2 + Map zRangeExampleParams2 = new HashMap<>(); + zRangeExampleParams2.put("one", 1.0); + zRangeExampleParams2.put("two", 2.0); + zRangeExampleParams2.put("three", 3.0); + long zRangeResult5 = jedis.zadd("myzset", zRangeExampleParams2); + System.out.println(zRangeResult5); // >>> 3 + + List zRangeResult6 = jedis.zrangeWithScores("myzset", new ZRangeParams(0, 1)); + + for (Tuple item: zRangeResult6) { + System.out.println("Element: " + item.getElement() + ", Score: " + item.getScore()); + } + // >>> Element: one, Score: 1.0 + // >>> Element: two, Score: 2.0 + // STEP_END + + // Tests for 'zrange2' step. + // REMOVE_START + Assert.assertEquals(3, zRangeResult5); + Assert.assertEquals(new Tuple("one", 1.0), zRangeResult6.get(0)); + Assert.assertEquals(new Tuple("two", 2.0), zRangeResult6.get(1)); + jedis.del("myzset"); + // REMOVE_END + + + // STEP_START zrange3 + Map zRangeExampleParams3 = new HashMap<>(); + zRangeExampleParams3.put("one", 1.0); + zRangeExampleParams3.put("two", 2.0); + zRangeExampleParams3.put("three", 3.0); + long zRangeResult7 = jedis.zadd("myzset", zRangeExampleParams3); + System.out.println(zRangeResult7); // >>> 3 + + List zRangeResult8 = jedis.zrangeByScore("myzset", "(1", "+inf", 1, 1); + System.out.println(String.join(", ", zRangeResult8)); // >>> three + // STEP_END + + // Tests for 'zrange3' step. + // REMOVE_START + Assert.assertEquals(3, zRangeResult7); + Assert.assertEquals("three", String.join(", ", zRangeResult8)); + jedis.del("myzset"); + // REMOVE_END + + + // STEP_START zrangebylex + + // STEP_END + + // Tests for 'zrangebylex' step. + // REMOVE_START + + // REMOVE_END + + + // STEP_START zrangebyscore + + // STEP_END + + // Tests for 'zrangebyscore' step. + // REMOVE_START + + // REMOVE_END + + + // STEP_START zrangestore + + // STEP_END + + // Tests for 'zrangestore' step. + // REMOVE_START + + // REMOVE_END + + + // STEP_START zrank + + // STEP_END + + // Tests for 'zrank' step. + // REMOVE_START + + // REMOVE_END + + + // STEP_START zrem + + // STEP_END + + // Tests for 'zrem' step. + // REMOVE_START + + // REMOVE_END + + + // STEP_START zremrangebylex + + // STEP_END + + // Tests for 'zremrangebylex' step. + // REMOVE_START + + // REMOVE_END + + + // STEP_START zremrangebyrank + + // STEP_END + + // Tests for 'zremrangebyrank' step. + // REMOVE_START + + // REMOVE_END + + + // STEP_START zremrangebyscore + + // STEP_END + + // Tests for 'zremrangebyscore' step. + // REMOVE_START + + // REMOVE_END + + + // STEP_START zrevrange + + // STEP_END + + // Tests for 'zrevrange' step. + // REMOVE_START + + // REMOVE_END + + + // STEP_START zrevrangebylex + + // STEP_END + + // Tests for 'zrevrangebylex' step. + // REMOVE_START + + // REMOVE_END + + + // STEP_START zrevrangebyscore + + // STEP_END + + // Tests for 'zrevrangebyscore' step. + // REMOVE_START + + // REMOVE_END + + + // STEP_START zrevrank + + // STEP_END + + // Tests for 'zrevrank' step. + // REMOVE_START + + // REMOVE_END + + + // STEP_START zscan + + // STEP_END + + // Tests for 'zscan' step. + // REMOVE_START + + // REMOVE_END + + + // STEP_START zscore + + // STEP_END + + // Tests for 'zscore' step. + // REMOVE_START + + // REMOVE_END + + + // STEP_START zunion + + // STEP_END + + // Tests for 'zunion' step. + // REMOVE_START + + // REMOVE_END + + + // STEP_START zunionstore + + // STEP_END + + // Tests for 'zunionstore' step. + // REMOVE_START + + // REMOVE_END + + +// HIDE_START + } +} +// HIDE_END + diff --git a/src/test/java/io/redis/examples/CmdsStringExample.java b/src/test/java/io/redis/examples/CmdsStringExample.java new file mode 100644 index 0000000000..a1ba0281e5 --- /dev/null +++ b/src/test/java/io/redis/examples/CmdsStringExample.java @@ -0,0 +1,48 @@ +// EXAMPLE: cmds_string +// REMOVE_START +package io.redis.examples; + +import org.junit.Assert; +import org.junit.Test; + +// REMOVE_END +// HIDE_START +import redis.clients.jedis.UnifiedJedis; +// HIDE_END + +// HIDE_START +public class CmdsStringExample { + @Test + public void run() { + UnifiedJedis jedis = new UnifiedJedis("redis://localhost:6379"); + + //REMOVE_START + // Clear any keys here before using them in tests. + jedis.del("mykey"); + //REMOVE_END +// HIDE_END + + // STEP_START incr + String incrResult1 = jedis.set("mykey", "10"); + System.out.println(incrResult1); // >>> OK + + long incrResult2 = jedis.incr("mykey"); + System.out.println(incrResult2); // >>> 11 + + String incrResult3 = jedis.get("mykey"); + System.out.println(incrResult3); // >>> 11 + // STEP_END + + // Tests for 'incr' step. + // REMOVE_START + Assert.assertEquals("OK", incrResult1); + Assert.assertEquals(11, incrResult2); + Assert.assertEquals("11", incrResult3); + jedis.del("mykey"); + // REMOVE_END + +// HIDE_START + } +} +// HIDE_END + diff --git a/src/test/java/redis/clients/jedis/EndpointConfig.java b/src/test/java/redis/clients/jedis/EndpointConfig.java index 42a44a3c47..68f927be0e 100644 --- a/src/test/java/redis/clients/jedis/EndpointConfig.java +++ b/src/test/java/redis/clients/jedis/EndpointConfig.java @@ -31,6 +31,10 @@ public HostAndPort getHostAndPort() { return JedisURIHelper.getHostAndPort(endpoints.get(0)); } + public HostAndPort getHostAndPort(int index) { + return JedisURIHelper.getHostAndPort(endpoints.get(index)); + } + public String getPassword() { return password; } diff --git a/src/test/java/redis/clients/jedis/scenario/ActiveActiveFailoverTest.java b/src/test/java/redis/clients/jedis/scenario/ActiveActiveFailoverTest.java new file mode 100644 index 0000000000..587988b447 --- /dev/null +++ b/src/test/java/redis/clients/jedis/scenario/ActiveActiveFailoverTest.java @@ -0,0 +1,187 @@ +package redis.clients.jedis.scenario; + +import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig; +import org.junit.BeforeClass; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import redis.clients.jedis.*; +import redis.clients.jedis.providers.MultiClusterPooledConnectionProvider; +import redis.clients.jedis.exceptions.JedisConnectionException; + +import java.io.IOException; +import java.time.Duration; +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; + +import static org.junit.Assert.*; + +public class ActiveActiveFailoverTest { + private static final Logger log = LoggerFactory.getLogger(ActiveActiveFailoverTest.class); + + private static EndpointConfig endpoint; + + private final FaultInjectionClient faultClient = new FaultInjectionClient(); + + @BeforeClass + public static void beforeClass() { + try { + ActiveActiveFailoverTest.endpoint = HostAndPorts.getRedisEndpoint("re-active-active"); + } catch (IllegalArgumentException e) { + log.warn("Skipping test because no Redis endpoint is configured"); + org.junit.Assume.assumeTrue(false); + } + } + + @Test + public void testFailover() { + + MultiClusterClientConfig.ClusterConfig[] clusterConfig = new MultiClusterClientConfig.ClusterConfig[2]; + + JedisClientConfig config = endpoint.getClientConfigBuilder() + .socketTimeoutMillis(RecommendedSettings.DEFAULT_TIMEOUT_MS) + .connectionTimeoutMillis(RecommendedSettings.DEFAULT_TIMEOUT_MS).build(); + + clusterConfig[0] = new MultiClusterClientConfig.ClusterConfig(endpoint.getHostAndPort(0), + config, RecommendedSettings.poolConfig); + clusterConfig[1] = new MultiClusterClientConfig.ClusterConfig(endpoint.getHostAndPort(1), + config, RecommendedSettings.poolConfig); + + MultiClusterClientConfig.Builder builder = new MultiClusterClientConfig.Builder(clusterConfig); + + builder.circuitBreakerSlidingWindowType(CircuitBreakerConfig.SlidingWindowType.TIME_BASED); + builder.circuitBreakerSlidingWindowSize(1); // SLIDING WINDOW SIZE IN SECONDS + builder.circuitBreakerSlidingWindowMinCalls(1); + builder.circuitBreakerFailureRateThreshold(10.0f); // percentage of failures to trigger circuit breaker + + builder.retryWaitDuration(10); + builder.retryMaxAttempts(1); + builder.retryWaitDurationExponentialBackoffMultiplier(1); + + class FailoverReporter implements Consumer { + + String currentClusterName = "not set"; + + boolean failoverHappened = false; + + Instant failoverAt = null; + + public String getCurrentClusterName() { + return currentClusterName; + } + + @Override + public void accept(String clusterName) { + this.currentClusterName = clusterName; + log.info( + "\n\n====FailoverEvent=== \nJedis failover to cluster: {}\n====FailoverEvent===\n\n", + clusterName); + + failoverHappened = true; + failoverAt = Instant.now(); + } + } + + MultiClusterPooledConnectionProvider provider = new MultiClusterPooledConnectionProvider( + builder.build()); + FailoverReporter reporter = new FailoverReporter(); + provider.setClusterFailoverPostProcessor(reporter); + provider.setActiveMultiClusterIndex(1); + + UnifiedJedis client = new UnifiedJedis(provider); + + AtomicLong retryingThreadsCounter = new AtomicLong(0); + AtomicLong failedCommandsAfterFailover = new AtomicLong(0); + AtomicReference lastFailedCommandAt = new AtomicReference<>(); + + // Start thread that imitates an application that uses the client + MultiThreadedFakeApp fakeApp = new MultiThreadedFakeApp(client, (UnifiedJedis c) -> { + + long threadId = Thread.currentThread().getId(); + + int attempt = 0; + int maxTries = 500; + int retryingDelay = 5; + while (true) { + try { + Map executionInfo = new HashMap() {{ + put("threadId", String.valueOf(threadId)); + put("cluster", reporter.getCurrentClusterName()); + }}; + client.xadd("execution_log", StreamEntryID.NEW_ENTRY, executionInfo); + + if (attempt > 0) { + log.info("Thread {} recovered after {} ms. Threads still not recovered: {}", threadId, + attempt * retryingDelay, retryingThreadsCounter.decrementAndGet()); + } + + break; + } catch (JedisConnectionException e) { + + if (reporter.failoverHappened) { + long failedCommands = failedCommandsAfterFailover.incrementAndGet(); + lastFailedCommandAt.set(Instant.now()); + log.warn( + "Thread {} failed to execute command after failover. Failed commands after failover: {}", + threadId, failedCommands); + } + + if (attempt == 0) { + long failedThreads = retryingThreadsCounter.incrementAndGet(); + log.warn("Thread {} failed to execute command. Failed threads: {}", threadId, + failedThreads); + } + try { + Thread.sleep(retryingDelay); + } catch (InterruptedException ie) { + throw new RuntimeException(ie); + } + if (++attempt == maxTries) throw e; + } + } + return true; + }, 18); + fakeApp.setKeepExecutingForSeconds(30); + Thread t = new Thread(fakeApp); + t.start(); + + HashMap params = new HashMap<>(); + params.put("bdb_id", endpoint.getBdbId()); + params.put("rlutil_command", "pause_bdb"); + + FaultInjectionClient.TriggerActionResponse actionResponse = null; + + try { + log.info("Triggering bdb_pause"); + actionResponse = faultClient.triggerAction("execute_rlutil_command", params); + } catch (IOException e) { + fail("Fault Injection Server error:" + e.getMessage()); + } + + log.info("Action id: {}", actionResponse.getActionId()); + fakeApp.setAction(actionResponse); + + try { + t.join(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + ConnectionPool pool = provider.getCluster(1).getConnectionPool(); + + log.info("First connection pool state: active: {}, idle: {}", pool.getNumActive(), + pool.getNumIdle()); + log.info("Full failover time: {} s", + Duration.between(reporter.failoverAt, lastFailedCommandAt.get()).getSeconds()); + + assertEquals(0, pool.getNumActive()); + assertTrue(fakeApp.capturedExceptions().isEmpty()); + + client.close(); + } + +} diff --git a/src/test/java/redis/clients/jedis/scenario/FakeApp.java b/src/test/java/redis/clients/jedis/scenario/FakeApp.java index 7e505862a2..4ea6ffc1f9 100644 --- a/src/test/java/redis/clients/jedis/scenario/FakeApp.java +++ b/src/test/java/redis/clients/jedis/scenario/FakeApp.java @@ -12,18 +12,18 @@ public class FakeApp implements Runnable { - private static final Logger log = LoggerFactory.getLogger(FakeApp.class); + protected static final Logger log = LoggerFactory.getLogger(FakeApp.class); public void setKeepExecutingForSeconds(int keepExecutingForSeconds) { this.keepExecutingForSeconds = keepExecutingForSeconds; } - private int keepExecutingForSeconds = 60; + protected int keepExecutingForSeconds = 60; - private FaultInjectionClient.TriggerActionResponse actionResponse = null; - private final UnifiedJedis client; - private final ExecutedAction action; - private List exceptions = new ArrayList<>(); + protected FaultInjectionClient.TriggerActionResponse actionResponse = null; + protected final UnifiedJedis client; + protected final ExecutedAction action; + protected List exceptions = new ArrayList<>(); @FunctionalInterface public interface ExecutedAction { diff --git a/src/test/java/redis/clients/jedis/scenario/MultiThreadedFakeApp.java b/src/test/java/redis/clients/jedis/scenario/MultiThreadedFakeApp.java new file mode 100644 index 0000000000..4c03fc2cf2 --- /dev/null +++ b/src/test/java/redis/clients/jedis/scenario/MultiThreadedFakeApp.java @@ -0,0 +1,48 @@ +package redis.clients.jedis.scenario; + +import redis.clients.jedis.UnifiedJedis; +import redis.clients.jedis.exceptions.JedisConnectionException; + +import java.time.Duration; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +public class MultiThreadedFakeApp extends FakeApp { + + private final ExecutorService executorService; + + public MultiThreadedFakeApp(UnifiedJedis client, FakeApp.ExecutedAction action, int numThreads) { + super(client, action); + this.executorService = Executors.newFixedThreadPool(numThreads); + } + + @Override + public void run() { + log.info("Starting FakeApp"); + + int checkEachSeconds = 5; + int timeoutSeconds = 120; + + while (actionResponse == null || !actionResponse.isCompleted( + Duration.ofSeconds(checkEachSeconds), Duration.ofSeconds(keepExecutingForSeconds), + Duration.ofSeconds(timeoutSeconds))) { + try { + executorService.submit(() -> action.run(client)); + } catch (JedisConnectionException e) { + log.error("Error executing action", e); + exceptions.add(e); + } + } + + executorService.shutdown(); + + try { + if (!executorService.awaitTermination(keepExecutingForSeconds, TimeUnit.SECONDS)) { + executorService.shutdownNow(); + } + } catch (InterruptedException e) { + log.error("Error waiting for executor service to terminate", e); + } + } +}