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",