diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveClusterServerCommands.java b/src/main/java/org/springframework/data/redis/connection/ReactiveClusterServerCommands.java new file mode 100644 index 000000000..eb441a223 --- /dev/null +++ b/src/main/java/org/springframework/data/redis/connection/ReactiveClusterServerCommands.java @@ -0,0 +1,125 @@ +/* + * Copyright 2017 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 + * + * http://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.connection; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.Properties; + +import org.springframework.data.redis.core.types.RedisClientInfo; + +/** + * @author Mark Paluch + * @since 2.0 + */ +public interface ReactiveClusterServerCommands extends ReactiveServerCommands { + + /** + * @param node must not be {@literal null}. + * @see RedisServerCommands#bgReWriteAof() + */ + Mono bgReWriteAof(RedisClusterNode node); + + /** + * @param node must not be {@literal null}. + * @see RedisServerCommands#bgSave() + */ + Mono bgSave(RedisClusterNode node); + + /** + * @param node must not be {@literal null}. + * @return + * @see RedisServerCommands#lastSave() + */ + Mono lastSave(RedisClusterNode node); + + /** + * @param node must not be {@literal null}. + * @see RedisServerCommands#save() + */ + Mono save(RedisClusterNode node); + + /** + * @param node must not be {@literal null}. + * @return + * @see RedisServerCommands#dbSize() + */ + Mono dbSize(RedisClusterNode node); + + /** + * @param node must not be {@literal null}. + * @see RedisServerCommands#flushDb() + */ + Mono flushDb(RedisClusterNode node); + + /** + * @param node must not be {@literal null}. + * @see RedisServerCommands#flushAll() + */ + Mono flushAll(RedisClusterNode node); + + /** + * @param node must not be {@literal null}. + * @return + * @see RedisServerCommands#info() + */ + Mono info(RedisClusterNode node); + + /** + * @param node must not be {@literal null}. + * @param section + * @return + * @see RedisServerCommands#info(String) + */ + Mono info(RedisClusterNode node, String section); + + /** + * @param node must not be {@literal null}. + * @param pattern + * @return + * @see RedisServerCommands#getConfig(String) + */ + Mono getConfig(RedisClusterNode node, String pattern); + + /** + * @param node must not be {@literal null}. + * @param param + * @param value + * @see RedisServerCommands#setConfig(String, String) + */ + Mono setConfig(RedisClusterNode node, String param, String value); + + /** + * @param node must not be {@literal null}. + * @see RedisServerCommands#resetConfigStats() + */ + Mono resetConfigStats(RedisClusterNode node); + + /** + * @param node must not be {@literal null}. + * @return + * @see RedisServerCommands#time() + */ + Mono time(RedisClusterNode node); + + /** + * @param node must not be {@literal null}. + * @return + * @see RedisServerCommands#getClientList() + */ + Flux getClientList(RedisClusterNode node); +} diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveRedisClusterConnection.java b/src/main/java/org/springframework/data/redis/connection/ReactiveRedisClusterConnection.java index 384623da1..df251593d 100644 --- a/src/main/java/org/springframework/data/redis/connection/ReactiveRedisClusterConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/ReactiveRedisClusterConnection.java @@ -15,12 +15,22 @@ */ package org.springframework.data.redis.connection; +import reactor.core.publisher.Mono; + /** * @author Christoph Strobl + * @author Mark Paluch * @since 2.0 */ public interface ReactiveRedisClusterConnection extends ReactiveRedisConnection { + /** + * @param node must not be {@literal null}. + * @return + * @see RedisConnectionCommands#ping() + */ + Mono ping(RedisClusterNode node); + @Override ReactiveClusterKeyCommands keyCommands(); @@ -47,4 +57,7 @@ public interface ReactiveRedisClusterConnection extends ReactiveRedisConnection @Override ReactiveClusterHyperLogLogCommands hyperLogLogCommands(); + + @Override + ReactiveClusterServerCommands serverCommands(); } diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveRedisConnection.java b/src/main/java/org/springframework/data/redis/connection/ReactiveRedisConnection.java index 01f87a36b..ab1d6424e 100644 --- a/src/main/java/org/springframework/data/redis/connection/ReactiveRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/ReactiveRedisConnection.java @@ -16,6 +16,7 @@ package org.springframework.data.redis.connection; import lombok.Data; +import reactor.core.publisher.Mono; import java.io.Closeable; import java.nio.ByteBuffer; @@ -114,6 +115,21 @@ public interface ReactiveRedisConnection extends Closeable { */ ReactiveHyperLogLogCommands hyperLogLogCommands(); + /** + * Get {@link ReactiveServerCommands}. + * + * @return never {@literal null}. + */ + ReactiveServerCommands serverCommands(); + + /** + * Test connection. + * + * @return Server response message - usually {@literal PONG}. + * @see Redis Documentation: PING + */ + Mono ping(); + /** * Base interface for Redis commands executed with a reactive infrastructure. * diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveServerCommands.java b/src/main/java/org/springframework/data/redis/connection/ReactiveServerCommands.java new file mode 100644 index 000000000..c6749f7f5 --- /dev/null +++ b/src/main/java/org/springframework/data/redis/connection/ReactiveServerCommands.java @@ -0,0 +1,174 @@ +/* + * Copyright 2017 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 + * + * http://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.connection; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.List; +import java.util.Properties; + +import org.springframework.data.redis.core.types.RedisClientInfo; + +/** + * Redis Server commands executed using reactive infrastructure. + * + * @author Mark Paluch + * @since 2.0 + */ +public interface ReactiveServerCommands { + + /** + * Start an {@literal Append Only File} rewrite process on server. + * + * @see Redis Documentation: BGREWRITEAOF + */ + Mono bgReWriteAof(); + + /** + * Start background saving of db on server. + * + * @see Redis Documentation: BGSAVE + */ + Mono bgSave(); + + /** + * Get time of last {@link #bgSave()} operation in seconds. + * + * @return + * @see Redis Documentation: LASTSAVE + */ + Mono lastSave(); + + /** + * Synchronous save current db snapshot on server. + * + * @see Redis Documentation: SAVE + */ + Mono save(); + + /** + * Get the total number of available keys in currently selected database. + * + * @return + * @see Redis Documentation: DBSIZE + */ + Mono dbSize(); + + /** + * Delete all keys of the currently selected database. + * + * @see Redis Documentation: FLUSHDB + */ + Mono flushDb(); + + /** + * Delete all all keys from all databases. + * + * @see Redis Documentation: FLUSHALL + */ + Mono flushAll(); + + /** + * Load {@literal default} server information like + *
    + *
  • memory
  • + *
  • cpu utilization
  • + *
  • replication
  • + *
+ *

+ * + * @return + * @see Redis Documentation: INFO + */ + Mono info(); + + /** + * Load server information for given {@code selection}. + * + * @param section + * @return + * @see Redis Documentation: INFO + */ + Mono info(String section); + + /** + * Load configuration parameters for given {@code pattern} from server. + * + * @param pattern + * @return + * @see Redis Documentation: CONFIG GET + */ + Mono getConfig(String pattern); + + /** + * Set server configuration for {@code param} to {@code value}. + * + * @param param + * @param value + * @see Redis Documentation: CONFIG SET + */ + Mono setConfig(String param, String value); + + /** + * Reset statistic counters on server.
+ * Counters can be retrieved using {@link #info()}. + * + * @see Redis Documentation: CONFIG RESETSTAT + */ + Mono resetConfigStats(); + + /** + * Request server timestamp using {@code TIME} command. + * + * @return current server time in milliseconds. + * @see Redis Documentation: TIME + */ + Mono time(); + + /** + * Closes a given client connection identified by {@literal host:port}. + * + * @param host of connection to close. + * @param port of connection to close + * @see Redis Documentation: CLIENT KILL + */ + Mono killClient(String host, int port); + + /** + * Assign given name to current connection. + * + * @param name + * @see Redis Documentation: CLIENT SETNAME + */ + Mono setClientName(String name); + + /** + * Returns the name of the current connection. + * + * @see Redis Documentation: CLIENT GETNAME + * @return + */ + Mono getClientName(); + + /** + * Request information and statistics about connected clients. + * + * @return {@link List} of {@link RedisClientInfo} objects. + * @see Redis Documentation: CLIENT LIST + */ + Flux getClientList(); +} diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnection.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnection.java index 06d236f87..046f558ca 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnection.java @@ -29,7 +29,6 @@ import io.lettuce.core.codec.RedisCodec; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -614,35 +613,4 @@ public class LettuceClusterConnection extends LettuceConnection implements Defau } } } - - /** - * Lettuce specific implementation of {@link ClusterTopologyProvider}. - * - * @author Christoph Strobl - * @since 1.7 - */ - static class LettuceClusterTopologyProvider implements ClusterTopologyProvider { - - private final RedisClusterClient client; - - /** - * @param client must not be {@literal null}. - */ - public LettuceClusterTopologyProvider(RedisClusterClient client) { - - Assert.notNull(client, "RedisClusterClient must not be null."); - this.client = client; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.redis.connection.ClusterTopologyProvider#getTopology() - */ - @Override - public ClusterTopology getTopology() { - return new ClusterTopology( - new LinkedHashSet<>(LettuceConverters.partitionsToClusterNodes(client.getPartitions()))); - } - } - } diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceClusterTopologyProvider.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceClusterTopologyProvider.java new file mode 100644 index 000000000..46949730b --- /dev/null +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceClusterTopologyProvider.java @@ -0,0 +1,55 @@ +/* + * Copyright 2017 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 + * + * http://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.connection.lettuce; + +import io.lettuce.core.cluster.RedisClusterClient; + +import java.util.LinkedHashSet; + +import org.springframework.data.redis.connection.ClusterTopology; +import org.springframework.data.redis.connection.ClusterTopologyProvider; +import org.springframework.util.Assert; + +/** + * Lettuce specific implementation of {@link ClusterTopologyProvider}. + * + * @author Christoph Strobl + * @author Mark Paluch + * @since 1.7 + */ +class LettuceClusterTopologyProvider implements ClusterTopologyProvider { + + private final RedisClusterClient client; + + /** + * @param client must not be {@literal null}. + */ + LettuceClusterTopologyProvider(RedisClusterClient client) { + + Assert.notNull(client, "RedisClusterClient must not be null."); + + this.client = client; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.ClusterTopologyProvider#getTopology() + */ + @Override + public ClusterTopology getTopology() { + return new ClusterTopology(new LinkedHashSet<>(LettuceConverters.partitionsToClusterNodes(client.getPartitions()))); + } +} diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactory.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactory.java index 6425fbb3e..227822456 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactory.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactory.java @@ -41,18 +41,7 @@ import org.springframework.dao.InvalidDataAccessResourceUsageException; import org.springframework.data.redis.ExceptionTranslationStrategy; import org.springframework.data.redis.PassThroughExceptionTranslationStrategy; import org.springframework.data.redis.RedisConnectionFailureException; -import org.springframework.data.redis.connection.ClusterCommandExecutor; -import org.springframework.data.redis.connection.Pool; -import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; -import org.springframework.data.redis.connection.RedisClusterConfiguration; -import org.springframework.data.redis.connection.RedisClusterConnection; -import org.springframework.data.redis.connection.RedisConnection; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.connection.RedisNode; -import org.springframework.data.redis.connection.RedisPassword; -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.*; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -772,7 +761,7 @@ public class LettuceConnectionFactory .orElseGet(() -> RedisClusterClient.create(initialUris)); this.clusterCommandExecutor = new ClusterCommandExecutor( - new LettuceClusterConnection.LettuceClusterTopologyProvider(clusterClient), + new LettuceClusterTopologyProvider(clusterClient), new LettuceClusterConnection.LettuceClusterNodeResourceProvider(clusterClient), EXCEPTION_TRANSLATION); clientConfiguration.getClientOptions() // diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConverters.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConverters.java index 314ca5e51..257a1a11c 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConverters.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConverters.java @@ -856,6 +856,27 @@ abstract public class LettuceConverters extends Converters { return geoArgs; } + /** + * Convert {@code CONFIG GET} output from a {@link List} to {@link Properties}. + * + * @param input must not be {@literal null}. + * @return the mapped result. + */ + public static Properties toProperties(List input) { + + Assert.notNull(input, "Input list must not be null!"); + Assert.isTrue(input.size() % 2 == 0, "Input list must contain an even number of entries!"); + + Properties properties = new Properties(); + + for (int i = 0; i < input.size(); i += 2) { + + properties.setProperty(input.get(i), input.get(i + 1)); + } + + return properties; + } + /** * Get {@link Converter} capable of {@link Set} of {@link Byte} into {@link GeoResults}. * diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveClusterServerCommands.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveClusterServerCommands.java new file mode 100644 index 000000000..4b696561c --- /dev/null +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveClusterServerCommands.java @@ -0,0 +1,337 @@ +/* + * Copyright 2017 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 + * + * http://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.connection.lettuce; + +import io.lettuce.core.api.reactive.RedisServerReactiveCommands; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.util.function.Tuple2; +import reactor.util.function.Tuples; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Map.Entry; +import java.util.Properties; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collector; + +import org.reactivestreams.Publisher; +import org.springframework.data.redis.connection.ClusterTopologyProvider; +import org.springframework.data.redis.connection.ReactiveClusterServerCommands; +import org.springframework.data.redis.connection.RedisClusterNode; +import org.springframework.data.redis.core.types.RedisClientInfo; +import org.springframework.data.redis.util.ByteUtils; + +/** + * @author Mark Paluch + * @since 2.0 + */ +class LettuceReactiveClusterServerCommands extends LettuceReactiveServerCommands + implements ReactiveClusterServerCommands { + + private final LettuceReactiveRedisClusterConnection connection; + private final ClusterTopologyProvider topologyProvider; + + /** + * Create new {@link LettuceReactiveGeoCommands}. + * + * @param connection must not be {@literal null}. + */ + public LettuceReactiveClusterServerCommands(LettuceReactiveRedisClusterConnection connection, + ClusterTopologyProvider topologyProvider) { + + super(connection); + + this.connection = connection; + this.topologyProvider = topologyProvider; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveClusterServerCommands#bgReWriteAof(org.springframework.data.redis.connection.RedisClusterNode) + */ + @Override + public Mono bgReWriteAof(RedisClusterNode node) { + return connection.execute(node, RedisServerReactiveCommands::bgrewriteaof).next(); + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveClusterServerCommands#bgSave(org.springframework.data.redis.connection.RedisClusterNode) + */ + @Override + public Mono bgSave(RedisClusterNode node) { + return connection.execute(node, RedisServerReactiveCommands::bgsave).next(); + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveClusterServerCommands#lastSave(org.springframework.data.redis.connection.RedisClusterNode) + */ + @Override + public Mono lastSave(RedisClusterNode node) { + return connection.execute(node, RedisServerReactiveCommands::lastsave).map(Date::getTime).next(); + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveClusterServerCommands#save(org.springframework.data.redis.connection.RedisClusterNode) + */ + @Override + public Mono save(RedisClusterNode node) { + return connection.execute(node, RedisServerReactiveCommands::save).next(); + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveClusterServerCommands#dbSize(org.springframework.data.redis.connection.RedisClusterNode) + */ + @Override + public Mono dbSize(RedisClusterNode node) { + return connection.execute(node, RedisServerReactiveCommands::dbsize).next(); + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveClusterServerCommands#flushDb(org.springframework.data.redis.connection.RedisClusterNode) + */ + @Override + public Mono flushDb(RedisClusterNode node) { + return connection.execute(node, RedisServerReactiveCommands::flushdb).next(); + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveClusterServerCommands#flushAll(org.springframework.data.redis.connection.RedisClusterNode) + */ + @Override + public Mono flushAll(RedisClusterNode node) { + return connection.execute(node, RedisServerReactiveCommands::flushall).next(); + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.lettuce.LettuceReactiveServerCommands#info() + */ + @Override + public Mono info() { + return Flux.merge(executeOnAllNodes(this::info)).collect(PropertiesCollector.INSTANCE); + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveClusterServerCommands#info(org.springframework.data.redis.connection.RedisClusterNode) + */ + @Override + public Mono info(RedisClusterNode node) { + + return connection.execute(node, RedisServerReactiveCommands::info) // + .map(LettuceConverters::toProperties) // + .next(); + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.lettuce.LettuceReactiveServerCommands#info(java.lang.String) + */ + @Override + public Mono info(String section) { + + return Flux.merge(executeOnAllNodes(redisClusterNode -> info(redisClusterNode, section))) + .collect(PropertiesCollector.INSTANCE); + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveClusterServerCommands#info(org.springframework.data.redis.connection.RedisClusterNode, java.lang.String) + */ + @Override + public Mono info(RedisClusterNode node, String section) { + + return connection.execute(node, c -> c.info(section)) // + .map(LettuceConverters::toProperties).next(); + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.lettuce.LettuceReactiveServerCommands#getConfig(java.lang.String) + */ + @Override + public Mono getConfig(String pattern) { + + return Flux.merge(executeOnAllNodes(node -> getConfig(node, pattern))) // + .collect(PropertiesCollector.INSTANCE); + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveClusterServerCommands#getConfig(org.springframework.data.redis.connection.RedisClusterNode, java.lang.String) + */ + @Override + public Mono getConfig(RedisClusterNode node, String pattern) { + + return connection.execute(node, c -> c.configGet(pattern).collectList()) // + .map(LettuceConverters::toProperties) // + .next(); + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.lettuce.LettuceReactiveServerCommands#setConfig(java.lang.String, java.lang.String) + */ + @Override + public Mono setConfig(String param, String value) { + return Flux.merge(executeOnAllNodes(node -> setConfig(node, param, value))).map(Tuple2::getT2).last(); + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveClusterServerCommands#setConfig(org.springframework.data.redis.connection.RedisClusterNode, java.lang.String, java.lang.String) + */ + @Override + public Mono setConfig(RedisClusterNode node, String param, String value) { + return connection.execute(node, c -> c.configSet(param, value)).next(); + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.lettuce.LettuceReactiveServerCommands#resetConfigStats() + */ + @Override + public Mono resetConfigStats() { + return Flux.merge(executeOnAllNodes(this::resetConfigStats)).map(Tuple2::getT2).last(); + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveClusterServerCommands#resetConfigStats(org.springframework.data.redis.connection.RedisClusterNode) + */ + @Override + public Mono resetConfigStats(RedisClusterNode node) { + return connection.execute(node, RedisServerReactiveCommands::configResetstat).next(); + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveClusterServerCommands#time(org.springframework.data.redis.connection.RedisClusterNode) + */ + @Override + public Mono time(RedisClusterNode node) { + + return connection.execute(node, RedisServerReactiveCommands::time) // + .map(ByteUtils::getBytes) // + .collectList() // + .map(LettuceConverters.toTimeConverter()::convert); + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.lettuce.LettuceReactiveServerCommands#getClientList() + */ + @Override + public Flux getClientList() { + return Flux.merge(executeOnAllNodesMany(this::getClientList)).map(Tuple2::getT2); + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveClusterServerCommands#getClientList(org.springframework.data.redis.connection.RedisClusterNode) + */ + @Override + public Flux getClientList(RedisClusterNode node) { + + return connection.execute(node, RedisServerReactiveCommands::clientList) + .flatMapIterable(LettuceConverters.stringToRedisClientListConverter()::convert); + } + + private Collection>> executeOnAllNodes( + Function> callback) { + + Set nodes = topologyProvider.getTopology().getNodes(); + List>> pipeline = new ArrayList<>(nodes.size()); + + for (RedisClusterNode redisClusterNode : nodes) { + pipeline.add(callback.apply(redisClusterNode).map(p -> Tuples.of(redisClusterNode, p))); + } + + return pipeline; + } + + private Collection>> executeOnAllNodesMany( + Function> callback) { + + Set nodes = topologyProvider.getTopology().getNodes(); + List>> pipeline = new ArrayList<>(nodes.size()); + + for (RedisClusterNode redisClusterNode : nodes) { + pipeline.add(callback.apply(redisClusterNode).map(p -> Tuples.of(redisClusterNode, p))); + } + + return pipeline; + } + + /** + * Collector to merge {@link Tuple2} of {@link RedisClusterNode} and {@link Properties} into a single + * {@link Properties} object by prefixing original the keys with {@link RedisClusterNode#asString()}. + */ + private enum PropertiesCollector implements Collector, Properties, Properties> { + + INSTANCE; + + /* (non-Javadoc) + * @see java.util.stream.Collector#supplier() + */ + @Override + public Supplier supplier() { + return Properties::new; + } + + /* (non-Javadoc) + * @see java.util.stream.Collector#accumulator() + */ + @Override + public BiConsumer> accumulator() { + + return (properties, tuple) -> { + + for (Entry entry : tuple.getT2().entrySet()) { + properties.put(tuple.getT1().asString() + "." + entry.getKey(), entry.getValue()); + } + }; + } + + /* (non-Javadoc) + * @see java.util.stream.Collector#combiner() + */ + @Override + public BinaryOperator combiner() { + + return (left, right) -> { + + Properties merge = new Properties(); + + merge.putAll(left); + merge.putAll(right); + + return merge; + }; + } + + /* (non-Javadoc) + * @see java.util.stream.Collector#finisher() + */ + @Override + public Function finisher() { + return properties -> properties; + } + + /* (non-Javadoc) + * @see java.util.stream.Collector#characteristics() + */ + @Override + public Set characteristics() { + return new HashSet<>(Arrays.asList(Characteristics.UNORDERED, Characteristics.IDENTITY_FINISH)); + } + } +} diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveRedisClusterConnection.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveRedisClusterConnection.java index c41ea22c0..d0ead4d8a 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveRedisClusterConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveRedisClusterConnection.java @@ -15,15 +15,19 @@ */ package org.springframework.data.redis.connection.lettuce; +import io.lettuce.core.api.reactive.BaseRedisReactiveCommands; import io.lettuce.core.api.reactive.RedisReactiveCommands; import io.lettuce.core.cluster.RedisClusterClient; import io.lettuce.core.cluster.api.StatefulRedisClusterConnection; import io.lettuce.core.cluster.api.reactive.RedisClusterReactiveCommands; import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; import java.nio.ByteBuffer; +import org.springframework.data.redis.connection.ClusterTopologyProvider; import org.springframework.data.redis.connection.ReactiveRedisClusterConnection; +import org.springframework.data.redis.connection.RedisClusterNode; import org.springframework.data.redis.connection.RedisNode; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -36,8 +40,13 @@ import org.springframework.util.StringUtils; class LettuceReactiveRedisClusterConnection extends LettuceReactiveRedisConnection implements ReactiveRedisClusterConnection { + private final ClusterTopologyProvider topologyProvider; + public LettuceReactiveRedisClusterConnection(RedisClusterClient client) { + super(client); + + this.topologyProvider = new LettuceClusterTopologyProvider(client); } /* (non-Javadoc) @@ -112,6 +121,22 @@ class LettuceReactiveRedisClusterConnection extends LettuceReactiveRedisConnecti return new LettuceReactiveClusterNumberCommands(this); } + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.lettuce.LettuceReactiveRedisConnection#serverCommands() + */ + @Override + public LettuceReactiveClusterServerCommands serverCommands() { + return new LettuceReactiveClusterServerCommands(this, topologyProvider); + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveRedisClusterConnection#ping(org.springframework.data.redis.connection.RedisClusterNode) + */ + @Override + public Mono ping(RedisClusterNode node) { + return execute(node, BaseRedisReactiveCommands::ping).next(); + } + /** * @param callback * @return @@ -119,8 +144,8 @@ class LettuceReactiveRedisClusterConnection extends LettuceReactiveRedisConnecti public Flux execute(RedisNode node, LettuceReactiveCallback callback) { try { + Assert.notNull(node, "RedisClusterNode must not be null!"); Assert.notNull(callback, "ReactiveCallback must not be null!"); - Assert.notNull(node, "Node must not be null!"); } catch (IllegalArgumentException e) { return Flux.error(e); } @@ -131,7 +156,7 @@ class LettuceReactiveRedisClusterConnection extends LettuceReactiveRedisConnecti /* (non-Javadoc) * @see org.springframework.data.redis.connection.lettuce.LettuceReactiveRedisConnection#getConnection() */ - @SuppressWarnings("unchecked") + @SuppressWarnings({ "unchecked", "rawtypes" }) @Override protected StatefulRedisClusterConnection getConnection() { @@ -148,7 +173,7 @@ class LettuceReactiveRedisClusterConnection extends LettuceReactiveRedisConnecti return getConnection().reactive(); } - @SuppressWarnings("unchecked") + @SuppressWarnings({ "unchecked", "rawtypes" }) protected RedisReactiveCommands getCommands(RedisNode node) { if (!(getConnection() instanceof StatefulRedisClusterConnection)) { diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveRedisConnection.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveRedisConnection.java index 4ec8fd69f..48430544d 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveRedisConnection.java @@ -19,11 +19,13 @@ import io.lettuce.core.AbstractRedisClient; import io.lettuce.core.RedisClient; import io.lettuce.core.api.StatefulConnection; import io.lettuce.core.api.StatefulRedisConnection; +import io.lettuce.core.api.reactive.BaseRedisReactiveCommands; import io.lettuce.core.cluster.RedisClusterClient; import io.lettuce.core.cluster.api.StatefulRedisClusterConnection; import io.lettuce.core.cluster.api.reactive.RedisClusterReactiveCommands; import io.lettuce.core.codec.RedisCodec; import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; import java.nio.ByteBuffer; import java.util.function.Function; @@ -31,16 +33,7 @@ import java.util.function.Function; import org.reactivestreams.Publisher; import org.springframework.dao.DataAccessException; import org.springframework.dao.InvalidDataAccessResourceUsageException; -import org.springframework.data.redis.connection.ReactiveGeoCommands; -import org.springframework.data.redis.connection.ReactiveHashCommands; -import org.springframework.data.redis.connection.ReactiveHyperLogLogCommands; -import org.springframework.data.redis.connection.ReactiveKeyCommands; -import org.springframework.data.redis.connection.ReactiveListCommands; -import org.springframework.data.redis.connection.ReactiveNumberCommands; -import org.springframework.data.redis.connection.ReactiveRedisConnection; -import org.springframework.data.redis.connection.ReactiveSetCommands; -import org.springframework.data.redis.connection.ReactiveStringCommands; -import org.springframework.data.redis.connection.ReactiveZSetCommands; +import org.springframework.data.redis.connection.*; import org.springframework.util.Assert; /** @@ -141,6 +134,22 @@ class LettuceReactiveRedisConnection implements ReactiveRedisConnection { return new LettuceReactiveHyperLogLogCommands(this); } + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveRedisConnection#hyperLogLogCommands() + */ + @Override + public ReactiveServerCommands serverCommands() { + return new LettuceReactiveServerCommands(this); + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveRedisConnection#ping() + */ + @Override + public Mono ping() { + return execute(BaseRedisReactiveCommands::ping).next(); + } + /** * @param callback * @return diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveServerCommands.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveServerCommands.java new file mode 100644 index 000000000..3340fa1eb --- /dev/null +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveServerCommands.java @@ -0,0 +1,206 @@ +/* + * Copyright 2017 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 + * + * http://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.connection.lettuce; + +import io.lettuce.core.api.reactive.RedisServerReactiveCommands; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.nio.ByteBuffer; +import java.util.Date; +import java.util.Properties; + +import org.springframework.data.redis.connection.ReactiveServerCommands; +import org.springframework.data.redis.core.types.RedisClientInfo; +import org.springframework.data.redis.util.ByteUtils; +import org.springframework.util.Assert; + +/** + * @author Mark Paluch + */ +class LettuceReactiveServerCommands implements ReactiveServerCommands { + + private final LettuceReactiveRedisConnection connection; + + /** + * Create new {@link LettuceReactiveGeoCommands}. + * + * @param connection must not be {@literal null}. + */ + public LettuceReactiveServerCommands(LettuceReactiveRedisConnection connection) { + + Assert.notNull(connection, "Connection must not be null!"); + this.connection = connection; + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveServerCommands#bgReWriteAof() + */ + @Override + public Mono bgReWriteAof() { + return connection.execute(RedisServerReactiveCommands::bgrewriteaof).next(); + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveServerCommands#bgSave() + */ + @Override + public Mono bgSave() { + return connection.execute(RedisServerReactiveCommands::bgsave).next(); + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveServerCommands#lastSave() + */ + @Override + public Mono lastSave() { + return connection.execute(RedisServerReactiveCommands::lastsave).next().map(Date::getTime); + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveServerCommands#save() + */ + @Override + public Mono save() { + return connection.execute(RedisServerReactiveCommands::save).next(); + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveServerCommands#dbSize() + */ + @Override + public Mono dbSize() { + return connection.execute(RedisServerReactiveCommands::dbsize).next(); + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveServerCommands#flushDb() + */ + @Override + public Mono flushDb() { + return connection.execute(RedisServerReactiveCommands::flushdb).next(); + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveServerCommands#flushAll() + */ + @Override + public Mono flushAll() { + return connection.execute(RedisServerReactiveCommands::flushall).next(); + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveServerCommands#info() + */ + @Override + public Mono info() { + + return connection.execute(RedisServerReactiveCommands::info) // + .map(LettuceConverters::toProperties) // + .next(); + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveServerCommands#info(java.lang.String) + */ + @Override + public Mono info(String section) { + + return connection.execute(c -> c.info(section)) // + .map(LettuceConverters::toProperties) // + .next(); + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveServerCommands#getConfig(java.lang.String) + */ + @Override + public Mono getConfig(String pattern) { + + return connection.execute(c -> c.configGet(pattern).collectList()) // + .map(LettuceConverters::toProperties).next(); + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveServerCommands#setConfig(java.lang.String, java.lang.String) + */ + @Override + public Mono setConfig(String param, String value) { + return connection.execute(c -> c.configSet(param, value)).next(); + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveServerCommands#resetConfigStats() + */ + @Override + public Mono resetConfigStats() { + return connection.execute(RedisServerReactiveCommands::configResetstat).next(); + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveServerCommands#time() + */ + @Override + public Mono time() { + + return connection.execute(RedisServerReactiveCommands::time) // + .map(ByteUtils::getBytes) // + .collectList() // + .map(LettuceConverters.toTimeConverter()::convert); + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveServerCommands#killClient(java.lang.String, int) + */ + @Override + public Mono killClient(String host, int port) { + + Assert.notNull(host, "Host must not be null"); + + String client = String.format("%s:%s", host, port); + return connection.execute(c -> c.clientKill(client)).next(); + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveServerCommands#setClientName(java.lang.String) + */ + @Override + public Mono setClientName(String name) { + return connection.execute(c -> c.clientSetname(ByteBuffer.wrap(LettuceConverters.toBytes(name)))).next(); + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveServerCommands#getClientName() + */ + @Override + public Mono getClientName() { + + return connection.execute(RedisServerReactiveCommands::clientGetname) // + .map(ByteUtils::getBytes) // + .map(LettuceConverters::toString) // + .next(); + } + + /* (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveServerCommands#getClientList() + */ + @Override + public Flux getClientList() { + + return connection.execute(RedisServerReactiveCommands::clientList) + .flatMapIterable(s -> LettuceConverters.stringToRedisClientListConverter().convert(s)); + } +} diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnectionUnitTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnectionUnitTests.java index 69188645a..d6a33164b 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnectionUnitTests.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnectionUnitTests.java @@ -104,7 +104,7 @@ public class LettuceClusterConnectionUnitTests { when(clusterMock.getPartitions()).thenReturn(partitions); ClusterCommandExecutor executor = new ClusterCommandExecutor( - new LettuceClusterConnection.LettuceClusterTopologyProvider(clusterMock), resourceProvider, + new LettuceClusterTopologyProvider(clusterMock), resourceProvider, LettuceClusterConnection.exceptionConverter); connection = new LettuceClusterConnection(clusterMock, executor) { diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveClusterServerCommandsTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveClusterServerCommandsTests.java new file mode 100644 index 000000000..c7939d01a --- /dev/null +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveClusterServerCommandsTests.java @@ -0,0 +1,170 @@ +/* + * Copyright 2017 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 + * + * http://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.connection.lettuce; + +import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.redis.connection.ClusterTestVariables.*; +import static org.springframework.data.redis.connection.lettuce.LettuceReactiveCommandsTestsBase.*; + +import reactor.test.StepVerifier; + +import org.junit.Test; +import org.springframework.data.redis.connection.RedisClusterNode; + +/** + * @author Mark Paluch + */ +public class LettuceReactiveClusterServerCommandsTests extends LettuceReactiveClusterCommandsTestsBase { + + static final RedisClusterNode NODE1 = new RedisClusterNode(CLUSTER_HOST, MASTER_NODE_1_PORT); + static final RedisClusterNode NODE2 = new RedisClusterNode(CLUSTER_HOST, MASTER_NODE_2_PORT); + static final RedisClusterNode NODE3 = new RedisClusterNode(CLUSTER_HOST, MASTER_NODE_3_PORT); + + @Test // DATAREDIS-659 + public void pingShouldRespondCorrectly() { + StepVerifier.create(connection.ping(NODE1)).expectNext("PONG").verifyComplete(); + } + + @Test // DATAREDIS-659 + public void bgReWriteAofShouldRespondCorrectly() { + StepVerifier.create(connection.serverCommands().bgReWriteAof(NODE1)).expectNextCount(1).verifyComplete(); + } + + @Test // DATAREDIS-659 + public void bgSaveShouldRespondCorrectly() { + StepVerifier.create(connection.serverCommands().bgSave(NODE1)).expectNextCount(1).verifyComplete(); + } + + @Test // DATAREDIS-659 + public void lastSaveShouldRespondCorrectly() { + StepVerifier.create(connection.serverCommands().lastSave(NODE1)).expectNextCount(1).verifyComplete(); + } + + @Test // DATAREDIS-659 + public void saveShouldRespondCorrectly() { + StepVerifier.create(connection.serverCommands().save(NODE1)).expectNext("OK").verifyComplete(); + } + + @Test // DATAREDIS-659 + public void dbSizeShouldRespondCorrectly() { + StepVerifier.create(connection.serverCommands().dbSize(NODE1)).expectNextCount(1).verifyComplete(); + } + + @Test // DATAREDIS-659 + public void flushDbShouldRespondCorrectly() { + + StepVerifier + .create(connection.serverCommands().flushDb() // + .then(connection.stringCommands().set(KEY_1_BBUFFER, VALUE_1_BBUFFER)) // + .then(connection.stringCommands().set(KEY_2_BBUFFER, VALUE_2_BBUFFER))) // + .expectNextCount(1) // + .verifyComplete(); + + StepVerifier.create(connection.serverCommands().dbSize(NODE1)).expectNext(1L).verifyComplete(); + StepVerifier.create(connection.serverCommands().dbSize(NODE3)).expectNext(1L).verifyComplete(); + + StepVerifier.create(connection.serverCommands().flushDb(NODE1)).expectNext("OK").verifyComplete(); + + StepVerifier.create(connection.serverCommands().dbSize(NODE1)).expectNext(0L).verifyComplete(); + StepVerifier.create(connection.serverCommands().dbSize(NODE3)).expectNext(1L).verifyComplete(); + } + + @Test // DATAREDIS-659 + public void flushAllShouldRespondCorrectly() { + + StepVerifier + .create(connection.serverCommands().flushAll() // + .then(connection.stringCommands().set(KEY_1_BBUFFER, VALUE_1_BBUFFER)) // + .then(connection.stringCommands().set(KEY_2_BBUFFER, VALUE_2_BBUFFER))) // + .expectNextCount(1) // + .verifyComplete(); + + StepVerifier.create(connection.serverCommands().dbSize(NODE1)).expectNext(1L).verifyComplete(); + StepVerifier.create(connection.serverCommands().dbSize(NODE3)).expectNext(1L).verifyComplete(); + + StepVerifier.create(connection.serverCommands().flushAll(NODE1)).expectNext("OK").verifyComplete(); + + StepVerifier.create(connection.serverCommands().dbSize(NODE1)).expectNext(0L).verifyComplete(); + StepVerifier.create(connection.serverCommands().dbSize(NODE3)).expectNext(1L).verifyComplete(); + } + + @Test // DATAREDIS-659 + public void infoShouldRespondCorrectly() { + + StepVerifier.create(connection.serverCommands().info(NODE1)) // + .consumeNextWith(properties -> assertThat(properties).containsKey("tcp_port")) // + .verifyComplete(); + } + + @Test // DATAREDIS-659 + public void standaloneInfoWithSectionShouldRespondCorrectly() { + + StepVerifier.create(connection.serverCommands().info(NODE1, "server")) // + .consumeNextWith(properties -> { + assertThat(properties).containsKey("tcp_port").doesNotContainKey("role"); + }) // + .verifyComplete(); + } + + @Test // DATAREDIS-659 + public void getConfigShouldRespondCorrectly() { + + StepVerifier.create(connection.serverCommands().getConfig(NODE1, "*")) // + .consumeNextWith(properties -> { + assertThat(properties).containsEntry("databases", "16"); + }) // + .verifyComplete(); + } + + @Test // DATAREDIS-659 + public void setConfigShouldApplyConfiguration() throws InterruptedException { + + StepVerifier.create(connection.serverCommands().setConfig("maxclients", "10000")) // + .expectNext("OK") // + .verifyComplete(); + + StepVerifier.create(connection.serverCommands().setConfig(NODE1, "maxclients", "9999")) // + .expectNext("OK") // + .verifyComplete(); + + StepVerifier.create(connection.serverCommands().getConfig(NODE1, "maxclients")) // + .consumeNextWith(properties -> { + assertThat(properties).containsEntry("maxclients", "9999"); + }) // + .verifyComplete(); + + StepVerifier.create(connection.serverCommands().getConfig(NODE2, "maxclients")) // + .consumeNextWith(properties -> { + assertThat(properties).containsEntry("maxclients", "10000"); + }) // + .verifyComplete(); + } + + @Test // DATAREDIS-659 + public void configResetstatShouldRespondCorrectly() { + StepVerifier.create(connection.serverCommands().resetConfigStats(NODE1)).expectNext("OK").verifyComplete(); + } + + @Test // DATAREDIS-659 + public void timeShouldRespondCorrectly() { + StepVerifier.create(connection.serverCommands().time(NODE1)).expectNextCount(1).verifyComplete(); + } + + @Test // DATAREDIS-659 + public void getClientListShouldReportClient() { + StepVerifier.create(connection.serverCommands().getClientList(NODE1)).expectNextCount(1).thenCancel().verify(); + } +} diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveServerCommandsTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveServerCommandsTests.java new file mode 100644 index 000000000..b77b4566c --- /dev/null +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveServerCommandsTests.java @@ -0,0 +1,202 @@ +/* + * Copyright 2017 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 + * + * http://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.connection.lettuce; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.Assume.*; + +import reactor.test.StepVerifier; + +import org.junit.Test; + +/** + * @author Mark Paluch + */ +public class LettuceReactiveServerCommandsTests extends LettuceReactiveCommandsTestsBase { + + @Test // DATAREDIS-659 + public void pingShouldRespondCorrectly() { + StepVerifier.create(connection.ping()).expectNext("PONG").verifyComplete(); + } + + @Test // DATAREDIS-659 + public void bgReWriteAofShouldRespondCorrectly() { + StepVerifier.create(connection.serverCommands().bgReWriteAof()).expectNextCount(1).verifyComplete(); + } + + @Test // DATAREDIS-659 + public void bgSaveShouldRespondCorrectly() { + StepVerifier.create(connection.serverCommands().bgSave()).expectNextCount(1).verifyComplete(); + } + + @Test // DATAREDIS-659 + public void lastSaveShouldRespondCorrectly() { + StepVerifier.create(connection.serverCommands().lastSave()).expectNextCount(1).verifyComplete(); + } + + @Test // DATAREDIS-659 + public void saveShouldRespondCorrectly() { + StepVerifier.create(connection.serverCommands().save()).expectNext("OK").verifyComplete(); + } + + @Test // DATAREDIS-659 + public void dbSizeShouldRespondCorrectly() { + StepVerifier.create(connection.serverCommands().dbSize()).expectNextCount(1).verifyComplete(); + } + + @Test // DATAREDIS-659 + public void flushDbShouldRespondCorrectly() { + + StepVerifier + .create(connection.serverCommands().flushDb() // + .then(connection.stringCommands().set(KEY_1_BBUFFER, VALUE_1_BBUFFER))) // + .expectNextCount(1) // + .verifyComplete(); + + StepVerifier.create(connection.serverCommands().dbSize()).expectNext(1L).verifyComplete(); + + StepVerifier.create(connection.serverCommands().flushDb()).expectNext("OK").verifyComplete(); + + StepVerifier.create(connection.serverCommands().dbSize()).expectNext(0L).verifyComplete(); + } + + @Test // DATAREDIS-659 + public void flushAllShouldRespondCorrectly() { + + StepVerifier + .create(connection.serverCommands().flushAll() // + .then(connection.stringCommands().set(KEY_1_BBUFFER, VALUE_1_BBUFFER))) // + .expectNextCount(1) // + .verifyComplete(); + + StepVerifier.create(connection.serverCommands().dbSize()).expectNext(1L).verifyComplete(); + + StepVerifier.create(connection.serverCommands().flushAll()).expectNext("OK").verifyComplete(); + + StepVerifier.create(connection.serverCommands().dbSize()).expectNext(0L).verifyComplete(); + } + + @Test // DATAREDIS-659 + public void infoShouldRespondCorrectly() { + + if (connection instanceof LettuceReactiveRedisClusterConnection) { + + StepVerifier.create(connection.serverCommands().info()) // + .consumeNextWith(properties -> { + + assertThat(properties) // + .containsKey("127.0.0.1:7379.tcp_port") // + .containsKey("127.0.0.1:7380.tcp_port"); + }) // + .verifyComplete(); + } else { + + StepVerifier.create(connection.serverCommands().info()) // + .consumeNextWith(properties -> assertThat(properties).containsKey("tcp_port")) // + .verifyComplete(); + } + } + + @Test // DATAREDIS-659 + public void standaloneInfoWithSectionShouldRespondCorrectly() { + + if (connection instanceof LettuceReactiveRedisClusterConnection) { + + StepVerifier.create(connection.serverCommands().info("server")) // + .consumeNextWith(properties -> { + assertThat(properties).isNotEmpty() // + .containsKey("127.0.0.1:7379.tcp_port") // + .doesNotContainKey("127.0.0.1:7379.role"); + }) // + .verifyComplete(); + } else { + + StepVerifier.create(connection.serverCommands().info("server")) // + .consumeNextWith(properties -> { + assertThat(properties).containsKey("tcp_port").doesNotContainKey("role"); + }) // + .verifyComplete(); + } + } + + @Test // DATAREDIS-659 + public void getConfigShouldRespondCorrectly() { + + if (connection instanceof LettuceReactiveRedisClusterConnection) { + + StepVerifier.create(connection.serverCommands().getConfig("*")) // + .consumeNextWith(properties -> { + assertThat(properties).containsEntry("127.0.0.1:7379.databases", "16"); + + }) // + .verifyComplete(); + } else { + + StepVerifier.create(connection.serverCommands().getConfig("*")) // + .consumeNextWith(properties -> { + assertThat(properties).containsEntry("databases", "16"); + }) // + .verifyComplete(); + } + } + + @Test // DATAREDIS-659 + public void setConfigShouldApplyConfiguration() { + + StepVerifier.create(connection.serverCommands().setConfig("maxclients", "9999")) // + .expectNext("OK") // + .verifyComplete(); + + if (connection instanceof LettuceReactiveRedisClusterConnection) { + StepVerifier.create(connection.serverCommands().getConfig("maxclients")) // + .consumeNextWith(properties -> { + assertThat(properties).containsEntry("127.0.0.1:7379.maxclients", "9999"); + }) // + .verifyComplete(); + } else { + StepVerifier.create(connection.serverCommands().getConfig("maxclients")) // + .consumeNextWith(properties -> { + assertThat(properties).containsEntry("maxclients", "9999"); + }) // + .verifyComplete(); + } + } + + @Test // DATAREDIS-659 + public void configResetstatShouldRespondCorrectly() { + StepVerifier.create(connection.serverCommands().resetConfigStats()).expectNext("OK").verifyComplete(); + } + + @Test // DATAREDIS-659 + public void timeShouldRespondCorrectly() { + StepVerifier.create(connection.serverCommands().time()).expectNextCount(1).verifyComplete(); + } + + @Test // DATAREDIS-659 + public void setClientNameShouldSetName() { + + // see lettuce-io/lettuce-core#563 + assumeFalse(connection instanceof LettuceReactiveRedisClusterConnection); + + StepVerifier.create(connection.serverCommands().setClientName("foo")).expectNextCount(1).verifyComplete(); + StepVerifier.create(connection.serverCommands().getClientName()).expectNext("foo").verifyComplete(); + } + + @Test // DATAREDIS-659 + public void getClientListShouldReportClient() { + StepVerifier.create(connection.serverCommands().getClientList()).expectNextCount(1).thenCancel().verify(); + } +}