GEODE-8711: Enable SLOWLOG Redis command (#5749)
diff --git a/geode-docs/tools_modules/redis_api_for_geode.html.md.erb b/geode-docs/tools_modules/redis_api_for_geode.html.md.erb
index 6bc90d3..5493404 100644
--- a/geode-docs/tools_modules/redis_api_for_geode.html.md.erb
+++ b/geode-docs/tools_modules/redis_api_for_geode.html.md.erb
@@ -81,7 +81,8 @@
- **Connection**: ECHO, SELECT
- **Hashes**: HDEL, HEXISTS, HGET, HINCRBY, HINCRBYFLOAT, HKEYS, HLEN, HMGET, HSCAN, HSETNX, HVALS
- **Keys**: SCAN, UNLINK
-- **Server**: DBSIZE, FLUSHALL (no async option), FLUSHDB (no async option), SHUTDOWN, TIME
+- **Server**: DBSIZE, FLUSHALL (no async option), FLUSHDB (no async option), SHUTDOWN, SLOWLOG,
+ TIME
- **Sets**: SCARD, SDIFF, SDIFFSTORE, SINTER, SINTERSTORE, SISMEMBER, SMOVE, SPOP, SRANDMEMBER,
SSCAN, SUNION, SUNIONSTORE
- **Strings**: BITCOUNT, BITOP, BITPOS, DECR, DECRBY, GETBIT, GETRANGE, GETSET, INCR, INCRBY, INCRBYFLOAT, MGET,
diff --git a/geode-redis/README.md b/geode-redis/README.md
index 5133522..130df87 100644
--- a/geode-redis/README.md
+++ b/geode-redis/README.md
@@ -200,16 +200,16 @@
| | SINTER | CLUSTER INFO |
| | SINTERSTORE | CLUSTER KEYSLOT |
| | SISMEMBER | CLUSTER MEET |
-| | SMOVE | CLUSTER MYID |
-| | SPOP | CLUSTER NODES |
-| | SRANDMEMBER | CLUSTER REPLICAS |
-| | SSCAN | CLUSTER REPLICATE |
-| | STRLEN | CLUSTER RESET |
-| | SUNION | CLUSTER SAVECONFIG |
-| | SUNIONSTORE | CLUSTER SET-CONFIG-EPOCH |
-| | TIME | CLUSTER SETSLOT |
-| | UNLINK [1] | CLUSTER SLAVES |
-| | | CLUSTER SLOTS |
+| | SLOWLOG | CLUSTER MYID |
+| | SMOVE | CLUSTER NODES |
+| | SPOP | CLUSTER REPLICAS |
+| | SRANDMEMBER | CLUSTER REPLICATE |
+| | SSCAN | CLUSTER RESET |
+| | STRLEN | CLUSTER SAVECONFIG |
+| | SUNION | CLUSTER SET-CONFIG-EPOCH |
+| | SUNIONSTORE | CLUSTER SETSLOT |
+| | TIME | CLUSTER SLAVES |
+| | UNLINK [1] | CLUSTER SLOTS |
| | | COMMAND |
| | | COMMAND COUNT |
| | | COMMAND GETKEYS |
@@ -289,7 +289,6 @@
| | | SCRIPT KILL |
| | | SCRIPT LOAD |
| | | SLAVEOF |
-| | | SLOWLOG |
| | | SORT |
| | | STRALGO LCS |
| | | SWAPDB |
diff --git a/geode-redis/src/acceptanceTest/java/org/apache/geode/redis/internal/executor/server/SlowlogNativeRedisAcceptanceTest.java b/geode-redis/src/acceptanceTest/java/org/apache/geode/redis/internal/executor/server/SlowlogNativeRedisAcceptanceTest.java
new file mode 100644
index 0000000..1fcb3d2
--- /dev/null
+++ b/geode-redis/src/acceptanceTest/java/org/apache/geode/redis/internal/executor/server/SlowlogNativeRedisAcceptanceTest.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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.apache.geode.redis.internal.executor.server;
+
+import org.junit.ClassRule;
+
+import org.apache.geode.NativeRedisTestRule;
+
+public class SlowlogNativeRedisAcceptanceTest extends AbstractSlowlogIntegrationTest {
+
+ @ClassRule
+ public static NativeRedisTestRule redis = new NativeRedisTestRule();
+
+ @Override
+ public int getPort() {
+ return redis.getPort();
+ }
+}
diff --git a/geode-redis/src/integrationTest/java/org/apache/geode/redis/internal/executor/server/AbstractSlowlogIntegrationTest.java b/geode-redis/src/integrationTest/java/org/apache/geode/redis/internal/executor/server/AbstractSlowlogIntegrationTest.java
new file mode 100644
index 0000000..669b310
--- /dev/null
+++ b/geode-redis/src/integrationTest/java/org/apache/geode/redis/internal/executor/server/AbstractSlowlogIntegrationTest.java
@@ -0,0 +1,151 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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.apache.geode.redis.internal.executor.server;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.Protocol;
+import redis.clients.jedis.util.Slowlog;
+
+import org.apache.geode.test.awaitility.GeodeAwaitility;
+import org.apache.geode.test.dunit.rules.RedisPortSupplier;
+
+public abstract class AbstractSlowlogIntegrationTest implements RedisPortSupplier {
+
+ private Jedis jedis;
+ private static final int REDIS_CLIENT_TIMEOUT =
+ Math.toIntExact(GeodeAwaitility.getTimeout().toMillis());
+
+ @Before
+ public void setUp() {
+ jedis = new Jedis("localhost", getPort(), REDIS_CLIENT_TIMEOUT);
+ }
+
+ @After
+ public void tearDown() {
+ jedis.close();
+ }
+
+ @Test
+ public void shouldReturnEmptyArray_whenGetSubcommandSpecified() {
+ List<Slowlog> actualResult = jedis.slowlogGet();
+
+ assertThat(actualResult.isEmpty());
+ }
+
+ @Test
+ public void shouldReturnEmptyArray_whenGetSubcommandSpecified_withLengthParameter() {
+ List<Slowlog> actualResult = jedis.slowlogGet(200);
+
+ assertThat(actualResult.isEmpty());
+ }
+
+ @Test
+ public void shouldReturnEmptyArray_whenGetSubcommandSpecified_withNegativeLengthParameter() {
+ List<Slowlog> actualResult = jedis.slowlogGet(-200);
+
+ assertThat(actualResult.isEmpty());
+ }
+
+ @Test
+ public void shouldNotThrowException_whenGetSubcommandSpecified_givenLongValue() {
+ List<Slowlog> actualResult = jedis.slowlogGet(Long.MAX_VALUE);
+
+ assertThat(actualResult.isEmpty());
+ }
+
+ @Test
+ public void shouldReturnZero_whenLenSubcommandSpecified() {
+ Long length = jedis.slowlogLen();
+
+ assertThat(length).isEqualTo(0L);
+ }
+
+ @Test
+ public void shouldReturnOK_whenResetSubcommandSpecified() {
+ String response = jedis.slowlogReset();
+
+ assertThat(response).isEqualTo("OK");
+ }
+
+ @Test
+ public void shouldThrowException_givenAnyExtraParameterIsProvidedToReset() {
+ assertThatThrownBy(
+ () -> jedis.sendCommand(
+ Protocol.Command.SLOWLOG, "RESET", "Superfluous"))
+ .hasMessage(
+ "ERR Unknown subcommand or wrong number of arguments for 'RESET'. Try SLOWLOG HELP.");
+ }
+
+ @Test
+ public void shouldThrowException_givenAnyExtraParameterIsProvidedToLenAParameter() {
+ assertThatThrownBy(
+ () -> jedis.sendCommand(
+ Protocol.Command.SLOWLOG, "LEN", "Superfluous"))
+ .hasMessage(
+ "ERR Unknown subcommand or wrong number of arguments for 'LEN'. Try SLOWLOG HELP.");
+ }
+
+ @Test
+ public void shouldMatchCaseOfSentCommand_whenThrowingException() {
+ assertThatThrownBy(
+ () -> jedis.sendCommand(
+ Protocol.Command.SLOWLOG, "lEn", "Superfluous"))
+ .hasMessage(
+ "ERR Unknown subcommand or wrong number of arguments for 'lEn'. Try SLOWLOG HELP.");
+ }
+
+ @Test
+ public void shouldThrowException_givenNonIntegerParameterToGET() {
+ assertThatThrownBy(
+ () -> jedis.sendCommand(
+ Protocol.Command.SLOWLOG, "GET", "I am not a number"))
+ .hasMessage("ERR value is not an integer or out of range");
+ }
+
+ @Test
+ public void shouldThrowException_givenMoreThanThreeArgumentsAfterCommand() {
+ assertThatThrownBy(
+ () -> jedis.sendCommand(
+ Protocol.Command.SLOWLOG, "secondArg", "thirdArg", "fourthArg"))
+ .hasMessage(
+ "ERR Unknown subcommand or wrong number of arguments for 'secondArg'. Try SLOWLOG HELP.");
+ }
+
+ @Test
+ public void shouldThrowException_givenUnknownSubcommand() {
+ assertThatThrownBy(
+ () -> jedis.sendCommand(
+ Protocol.Command.SLOWLOG, "xlerb"))
+ .hasMessage(
+ "ERR Unknown subcommand or wrong number of arguments for 'xlerb'. Try SLOWLOG HELP.");
+ }
+
+ @Test
+ public void shouldThrowException_givenNoSubcommand() {
+ assertThatThrownBy(
+ () -> jedis.sendCommand(
+ Protocol.Command.SLOWLOG))
+ .hasMessage("ERR wrong number of arguments for 'slowlog' command");
+ }
+}
diff --git a/geode-redis/src/integrationTest/java/org/apache/geode/redis/internal/executor/server/SlowlogIntegrationTest.java b/geode-redis/src/integrationTest/java/org/apache/geode/redis/internal/executor/server/SlowlogIntegrationTest.java
new file mode 100644
index 0000000..e768193
--- /dev/null
+++ b/geode-redis/src/integrationTest/java/org/apache/geode/redis/internal/executor/server/SlowlogIntegrationTest.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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.apache.geode.redis.internal.executor.server;
+
+import static org.apache.geode.distributed.ConfigurationProperties.LOG_LEVEL;
+
+import org.junit.ClassRule;
+
+import org.apache.geode.redis.GeodeRedisServerRule;
+
+public class SlowlogIntegrationTest extends AbstractSlowlogIntegrationTest {
+
+ @ClassRule
+ public static GeodeRedisServerRule server = new GeodeRedisServerRule()
+ .withProperty(LOG_LEVEL, "info");
+
+ @Override
+ public int getPort() {
+ return server.getPort();
+ }
+}
diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/ParameterRequirements/SlowlogParameterRequirements.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/ParameterRequirements/SlowlogParameterRequirements.java
new file mode 100644
index 0000000..4950e19
--- /dev/null
+++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/ParameterRequirements/SlowlogParameterRequirements.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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.apache.geode.redis.internal.ParameterRequirements;
+
+import static org.apache.geode.redis.internal.RedisConstants.ERROR_NOT_INTEGER;
+import static org.apache.geode.redis.internal.RedisConstants.ERROR_UNKNOWN_SLOWLOG_SUBCOMMAND;
+
+import org.apache.geode.redis.internal.netty.Command;
+import org.apache.geode.redis.internal.netty.ExecutionHandlerContext;
+
+public class SlowlogParameterRequirements implements ParameterRequirements {
+ @Override
+ public void checkParameters(Command command, ExecutionHandlerContext context) {
+ int numberOfArguments = command.getProcessedCommand().size();
+
+ if (numberOfArguments < 2) {
+ throw new RedisParametersMismatchException(command.wrongNumberOfArgumentsErrorMessage());
+ } else if (numberOfArguments == 2) {
+ confirmKnownSubcommands(command);
+ } else if (numberOfArguments == 3) {
+ confirmArgumentsToGetSubcommand(command);
+ } else { // numberOfArguments > 3
+ throw new RedisParametersMismatchException(
+ String.format(ERROR_UNKNOWN_SLOWLOG_SUBCOMMAND, command.getStringKey()));
+ }
+ }
+
+ private void confirmKnownSubcommands(Command command) {
+ if (!command.getStringKey().toLowerCase().equals("reset") &&
+ !command.getStringKey().toLowerCase().equals("len") &&
+ !command.getStringKey().toLowerCase().equals("get")) {
+ throw new RedisParametersMismatchException(
+ String.format(ERROR_UNKNOWN_SLOWLOG_SUBCOMMAND, command.getStringKey()));
+ }
+ }
+
+ private void confirmArgumentsToGetSubcommand(Command command) {
+ if (!command.getStringKey().toLowerCase().equals("get")) {
+ throw new RedisParametersMismatchException(
+ String.format(ERROR_UNKNOWN_SLOWLOG_SUBCOMMAND, command.getStringKey()));
+ }
+ try {
+ Long.parseLong(new String(command.getProcessedCommand().get(2)));
+ } catch (NumberFormatException nex) {
+ throw new RedisParametersMismatchException(ERROR_NOT_INTEGER);
+ }
+ }
+
+}
diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/RedisCommandType.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/RedisCommandType.java
index 8c0ebf1..f06b495 100755
--- a/geode-redis/src/main/java/org/apache/geode/redis/internal/RedisCommandType.java
+++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/RedisCommandType.java
@@ -26,6 +26,7 @@
import org.apache.geode.redis.internal.ParameterRequirements.MinimumParameterRequirements;
import org.apache.geode.redis.internal.ParameterRequirements.OddParameterRequirements;
import org.apache.geode.redis.internal.ParameterRequirements.ParameterRequirements;
+import org.apache.geode.redis.internal.ParameterRequirements.SlowlogParameterRequirements;
import org.apache.geode.redis.internal.ParameterRequirements.SpopParameterRequirements;
import org.apache.geode.redis.internal.ParameterRequirements.UnspecifiedParameterRequirements;
import org.apache.geode.redis.internal.executor.Executor;
@@ -73,6 +74,7 @@
import org.apache.geode.redis.internal.executor.server.FlushAllExecutor;
import org.apache.geode.redis.internal.executor.server.InfoExecutor;
import org.apache.geode.redis.internal.executor.server.ShutDownExecutor;
+import org.apache.geode.redis.internal.executor.server.SlowlogExecutor;
import org.apache.geode.redis.internal.executor.server.TimeExecutor;
import org.apache.geode.redis.internal.executor.set.SAddExecutor;
import org.apache.geode.redis.internal.executor.set.SCardExecutor;
@@ -265,6 +267,7 @@
FLUSHDB(new FlushAllExecutor(), UNSUPPORTED, new MaximumParameterRequirements(2, ERROR_SYNTAX)),
INFO(new InfoExecutor(), UNSUPPORTED, new MaximumParameterRequirements(2, ERROR_SYNTAX)),
SHUTDOWN(new ShutDownExecutor(), UNSUPPORTED, new MaximumParameterRequirements(2, ERROR_SYNTAX)),
+ SLOWLOG(new SlowlogExecutor(), UNSUPPORTED, new SlowlogParameterRequirements()),
TIME(new TimeExecutor(), UNSUPPORTED, new ExactParameterRequirements(1)),
/////////// UNIMPLEMENTED /////////////////////
@@ -333,7 +336,6 @@
SCRIPT(null, UNIMPLEMENTED),
SLAVEOF(null, UNIMPLEMENTED),
REPLICAOF(null, UNIMPLEMENTED),
- SLOWLOG(null, UNIMPLEMENTED),
SORT(null, UNIMPLEMENTED),
STRALGO(null, UNIMPLEMENTED),
SWAPDB(null, UNIMPLEMENTED),
diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/RedisConstants.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/RedisConstants.java
index acfeefe..0a86362 100644
--- a/geode-redis/src/main/java/org/apache/geode/redis/internal/RedisConstants.java
+++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/RedisConstants.java
@@ -50,5 +50,6 @@
public static final String ERROR_INVALID_EXPIRE_TIME = "invalid expire time in set";
public static final String ERROR_NOT_A_VALID_FLOAT = "value is not a valid float";
-
+ public static final String ERROR_UNKNOWN_SLOWLOG_SUBCOMMAND =
+ "Unknown subcommand or wrong number of arguments for '%s'. Try SLOWLOG HELP.";
}
diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/server/SlowlogExecutor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/server/SlowlogExecutor.java
index 4298a90..be5bce9 100644
--- a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/server/SlowlogExecutor.java
+++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/server/SlowlogExecutor.java
@@ -15,10 +15,6 @@
package org.apache.geode.redis.internal.executor.server;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.geode.redis.internal.data.ByteArrayWrapper;
import org.apache.geode.redis.internal.executor.AbstractExecutor;
import org.apache.geode.redis.internal.executor.RedisResponse;
import org.apache.geode.redis.internal.netty.Command;
@@ -30,7 +26,16 @@
@Override
public RedisResponse executeCommand(Command command,
ExecutionHandlerContext context) {
- List<ByteArrayWrapper> values = new ArrayList<ByteArrayWrapper>();
- return RedisResponse.array(values);
+ String subCommand = command.getStringKey().toLowerCase();
+ switch (subCommand) {
+ case "get":
+ return RedisResponse.emptyArray();
+ case "len":
+ return RedisResponse.integer(0);
+ case "reset":
+ return RedisResponse.ok();
+ default:
+ return null; // Should never happen
+ }
}
}
diff --git a/geode-redis/src/test/java/org/apache/geode/redis/internal/SupportedCommandsJUnitTest.java b/geode-redis/src/test/java/org/apache/geode/redis/internal/SupportedCommandsJUnitTest.java
index 751bed9..fa2872e 100644
--- a/geode-redis/src/test/java/org/apache/geode/redis/internal/SupportedCommandsJUnitTest.java
+++ b/geode-redis/src/test/java/org/apache/geode/redis/internal/SupportedCommandsJUnitTest.java
@@ -108,6 +108,7 @@
"SINTER",
"SINTERSTORE",
"SISMEMBER",
+ "SLOWLOG",
"SMOVE",
"SPOP",
"SRANDMEMBER",
@@ -184,7 +185,6 @@
"SCRIPT",
"SLAVEOF",
"REPLICAOF",
- "SLOWLOG",
"SORT",
"STRALGO",
"SWAPDB",