This is an automated email from the ASF dual-hosted git repository.

krathbun pushed a commit to branch 3.1
in repository https://gitbox.apache.org/repos/asf/accumulo.git


The following commit(s) were added to refs/heads/3.1 by this push:
     new 33cff1360b New `admin check` command structure implementation (#4807)
33cff1360b is described below

commit 33cff1360bb499a312b992bb9aa385e557b77f4f
Author: Kevin Rathbun <krath...@apache.org>
AuthorDate: Mon Sep 16 11:18:16 2024 -0400

    New `admin check` command structure implementation (#4807)
    
    (Part of https://github.com/apache/accumulo/issues/4687)
    - Added new `admin check` command
    - `admin check list` prints the checks that can be run along with their 
descriptions and dependencies
    - `admin check run` runs all the checks or a specified list of checks 
(explicitly provide each check, provide a regex of the checks to run, or just 
run all if no further args are provided). The dependencies are run first and if 
a checks dependency fails, the check is skipped
    - Current impl of `admin check run` does not do any actual work besides 
printing that the check is running (to ensure correct run order) and returning 
an `OK` status
    - New IT AdminCheckIT
            - This IT includes a verion of the Checks where no work is actually 
done (right now, this is equivalent to the actual implementation). This is done 
so correct run order, correct checks run, etc. can be
            verified without actually running the checks which may take a long 
time. More tests can be added later to test the actual check functionality when 
that is implemented.
    - No existing functionality was changed
---
 .../org/apache/accumulo/server/util/Admin.java     | 196 ++++++++++
 .../server/util/checkCommand/CheckRunner.java      |  38 ++
 .../checkCommand/MetadataTableCheckRunner.java     |  40 ++
 .../util/checkCommand/RootMetadataCheckRunner.java |  40 ++
 .../util/checkCommand/RootTableCheckRunner.java    |  40 ++
 .../util/checkCommand/SystemConfigCheckRunner.java |  40 ++
 .../util/checkCommand/SystemFilesCheckRunner.java  |  40 ++
 .../util/checkCommand/UserFilesCheckRunner.java    |  42 +++
 .../org/apache/accumulo/test/AdminCheckIT.java     | 411 +++++++++++++++++++++
 9 files changed, 887 insertions(+)

diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/Admin.java 
b/server/base/src/main/java/org/apache/accumulo/server/util/Admin.java
index f6571c0218..781037f66e 100644
--- a/server/base/src/main/java/org/apache/accumulo/server/util/Admin.java
+++ b/server/base/src/main/java/org/apache/accumulo/server/util/Admin.java
@@ -35,11 +35,15 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Objects;
 import java.util.Set;
 import java.util.SortedSet;
 import java.util.TreeMap;
 import java.util.TreeSet;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Supplier;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 
 import org.apache.accumulo.core.Constants;
 import org.apache.accumulo.core.client.AccumuloClient;
@@ -76,6 +80,13 @@ import org.apache.accumulo.core.util.tables.TableMap;
 import org.apache.accumulo.server.ServerContext;
 import org.apache.accumulo.server.cli.ServerUtilOpts;
 import org.apache.accumulo.server.security.SecurityUtil;
+import org.apache.accumulo.server.util.checkCommand.CheckRunner;
+import org.apache.accumulo.server.util.checkCommand.MetadataTableCheckRunner;
+import org.apache.accumulo.server.util.checkCommand.RootMetadataCheckRunner;
+import org.apache.accumulo.server.util.checkCommand.RootTableCheckRunner;
+import org.apache.accumulo.server.util.checkCommand.SystemConfigCheckRunner;
+import org.apache.accumulo.server.util.checkCommand.SystemFilesCheckRunner;
+import org.apache.accumulo.server.util.checkCommand.UserFilesCheckRunner;
 import org.apache.accumulo.server.util.fateCommand.FateSummaryReport;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 import org.apache.zookeeper.KeeperException;
@@ -86,6 +97,8 @@ import com.beust.jcommander.JCommander;
 import com.beust.jcommander.Parameter;
 import com.beust.jcommander.Parameters;
 import com.google.auto.service.AutoService;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableSortedMap;
 import com.google.common.collect.Lists;
 import com.google.common.net.HostAndPort;
@@ -114,6 +127,93 @@ public class Admin implements KeywordExecutable {
     List<String> args = new ArrayList<>();
   }
 
+  @Parameters(commandNames = "check",
+      commandDescription = "Performs checks for problems in Accumulo.")
+  public static class CheckCommand {
+    @Parameter(names = "list",
+        description = "Lists the different checks that can be run, the 
description of each check, and the other check(s) each check depends on.")
+    boolean list;
+
+    @Parameter(names = "run",
+        description = "Runs the provided check(s) (explicit list or regex 
pattern specified following '-p'), beginning with their dependencies, or all 
checks if none are provided.")
+    boolean run;
+
+    @Parameter(names = {"--name_pattern", "-p"},
+        description = "Runs all checks that match the provided regex pattern.")
+    String pattern;
+
+    @Parameter(description = "[<Check>...]")
+    List<String> checks;
+
+    /**
+     * This should be used to get the check runner instead of {@link 
Check#getCheckRunner()}. This
+     * exists so that its functionality can be changed for testing.
+     *
+     * @return the interface for running a check
+     */
+    public CheckRunner getCheckRunner(Check check) {
+      return check.getCheckRunner();
+    }
+
+    public enum Check {
+      // Caution should be taken when changing or adding any new checks: order 
is important
+      SYSTEM_CONFIG(SystemConfigCheckRunner::new, "Validate the system config 
stored in ZooKeeper",
+          Collections.emptyList()),
+      ROOT_METADATA(RootMetadataCheckRunner::new,
+          "Checks integrity of the root tablet metadata stored in ZooKeeper",
+          Collections.singletonList(SYSTEM_CONFIG)),
+      ROOT_TABLE(RootTableCheckRunner::new,
+          "Scans all the tablet metadata stored in the root table and checks 
integrity",
+          Collections.singletonList(ROOT_METADATA)),
+      METADATA_TABLE(MetadataTableCheckRunner::new,
+          "Scans all the tablet metadata stored in the metadata table and 
checks integrity",
+          Collections.singletonList(ROOT_TABLE)),
+      SYSTEM_FILES(SystemFilesCheckRunner::new,
+          "Checks that files in system tablet metadata exist in DFS",
+          Collections.singletonList(ROOT_TABLE)),
+      USER_FILES(UserFilesCheckRunner::new,
+          "Checks that files in user tablet metadata exist in DFS",
+          Collections.singletonList(METADATA_TABLE));
+
+      private final Supplier<CheckRunner> checkRunner;
+      private final String description;
+      private final List<Check> dependencies;
+
+      Check(Supplier<CheckRunner> checkRunner, String description, List<Check> 
dependencies) {
+        this.checkRunner = Objects.requireNonNull(checkRunner);
+        this.description = Objects.requireNonNull(description);
+        this.dependencies = Objects.requireNonNull(dependencies);
+      }
+
+      /**
+       * This should not be called directly; use {@link 
CheckCommand#getCheckRunner(Check)} instead
+       *
+       * @return the interface for running a check
+       */
+      public CheckRunner getCheckRunner() {
+        return checkRunner.get();
+      }
+
+      /**
+       * @return the description of the check
+       */
+      public String getDescription() {
+        return description;
+      }
+
+      /**
+       * @return the list of other checks the check depends on
+       */
+      public List<Check> getDependencies() {
+        return dependencies;
+      }
+    }
+
+    public enum CheckStatus {
+      OK, FAILED, SKIPPED_DEPENDENCY_FAILED, FILTERED_OUT;
+    }
+  }
+
   @Parameters(commandDescription = "print tablets that are offline in online 
tables")
   static class CheckTabletsCommand {
     @Parameter(names = "--fixFiles", description = "Remove dangling file 
pointers")
@@ -274,6 +374,9 @@ public class Admin implements KeywordExecutable {
     ChangeSecretCommand changeSecretCommand = new ChangeSecretCommand();
     cl.addCommand("changeSecret", changeSecretCommand);
 
+    CheckCommand checkCommand = new CheckCommand();
+    cl.addCommand("check", checkCommand);
+
     CheckTabletsCommand checkTabletsCommand = new CheckTabletsCommand();
     cl.addCommand("checkTablets", checkTabletsCommand);
 
@@ -380,6 +483,8 @@ public class Admin implements KeywordExecutable {
         executeFateOpsCommand(context, fateOpsCommand);
       } else if (cl.getParsedCommand().equals("serviceStatus")) {
         printServiceStatus(context, serviceStatusCommandOpts);
+      } else if (cl.getParsedCommand().equals("check")) {
+        executeCheckCommand(context, checkCommand);
       } else {
         everything = cl.getParsedCommand().equals("stopAll");
 
@@ -907,4 +1012,95 @@ public class Admin implements KeywordExecutable {
     }
     return statusFilter;
   }
+
+  @VisibleForTesting
+  public static void executeCheckCommand(ServerContext context, CheckCommand 
cmd) {
+    validateAndTransformCheckCommand(cmd);
+
+    if (cmd.list) {
+      listChecks();
+    } else if (cmd.run) {
+      var givenChecks =
+          
cmd.checks.stream().map(CheckCommand.Check::valueOf).collect(Collectors.toList());
+      executeRunCheckCommand(cmd, givenChecks);
+    }
+  }
+
+  private static void validateAndTransformCheckCommand(CheckCommand cmd) {
+    Preconditions.checkArgument(cmd.list != cmd.run, "Must use either 'list' 
or 'run'");
+    if (cmd.list) {
+      Preconditions.checkArgument(cmd.checks == null && cmd.pattern == null,
+          "'list' does not expect any further arguments");
+    } else if (cmd.pattern != null) {
+      Preconditions.checkArgument(cmd.checks == null, "Expected one argument 
(the regex pattern)");
+      List<String> matchingChecks = new ArrayList<>();
+      var pattern = Pattern.compile(cmd.pattern.toUpperCase());
+      for (CheckCommand.Check check : CheckCommand.Check.values()) {
+        if (pattern.matcher(check.name()).matches()) {
+          matchingChecks.add(check.name());
+        }
+      }
+      Preconditions.checkArgument(!matchingChecks.isEmpty(),
+          "No checks matched the given pattern: " + pattern.pattern());
+      cmd.checks = matchingChecks;
+    } else {
+      if (cmd.checks == null) {
+        cmd.checks = 
EnumSet.allOf(CheckCommand.Check.class).stream().map(Enum::name)
+            .collect(Collectors.toList());
+      }
+    }
+  }
+
+  private static void listChecks() {
+    System.out.println();
+    System.out.printf("%-20s | %-80s | %-20s%n", "Check Name", "Description", 
"Depends on");
+    System.out.println("-".repeat(120));
+    for (CheckCommand.Check check : CheckCommand.Check.values()) {
+      System.out.printf("%-20s | %-80s | %-20s%n", check.name(), 
check.getDescription(),
+          check.getDependencies().stream().map(CheckCommand.Check::name)
+              .collect(Collectors.joining(", ")));
+    }
+    System.out.println("-".repeat(120));
+    System.out.println();
+  }
+
+  private static void executeRunCheckCommand(CheckCommand cmd,
+      List<CheckCommand.Check> givenChecks) {
+    // Get all the checks in the order they are declared in the enum
+    final var allChecks = CheckCommand.Check.values();
+    final TreeMap<CheckCommand.Check,CheckCommand.CheckStatus> checkStatus = 
new TreeMap<>();
+
+    for (CheckCommand.Check check : allChecks) {
+      if (depsFailed(check, checkStatus)) {
+        checkStatus.put(check, 
CheckCommand.CheckStatus.SKIPPED_DEPENDENCY_FAILED);
+      } else {
+        if (givenChecks.contains(check)) {
+          checkStatus.put(check, cmd.getCheckRunner(check).runCheck());
+        } else {
+          checkStatus.put(check, CheckCommand.CheckStatus.FILTERED_OUT);
+        }
+      }
+    }
+
+    printChecksResults(checkStatus);
+  }
+
+  private static boolean depsFailed(CheckCommand.Check check,
+      TreeMap<CheckCommand.Check,CheckCommand.CheckStatus> checkStatus) {
+    return check.getDependencies().stream()
+        .anyMatch(dep -> checkStatus.get(dep) == 
CheckCommand.CheckStatus.FAILED
+            || checkStatus.get(dep) == 
CheckCommand.CheckStatus.SKIPPED_DEPENDENCY_FAILED);
+  }
+
+  private static void
+      printChecksResults(TreeMap<CheckCommand.Check,CheckCommand.CheckStatus> 
checkStatus) {
+    System.out.println();
+    System.out.printf("%-20s | %-20s%n", "Check Name", "Status");
+    System.out.println("-".repeat(50));
+    for (Map.Entry<CheckCommand.Check,CheckCommand.CheckStatus> entry : 
checkStatus.entrySet()) {
+      System.out.printf("%-20s | %-20s%n", entry.getKey().name(), 
entry.getValue().name());
+    }
+    System.out.println("-".repeat(50));
+    System.out.println();
+  }
 }
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/checkCommand/CheckRunner.java
 
b/server/base/src/main/java/org/apache/accumulo/server/util/checkCommand/CheckRunner.java
new file mode 100644
index 0000000000..2e1377e3ed
--- /dev/null
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/util/checkCommand/CheckRunner.java
@@ -0,0 +1,38 @@
+/*
+ * 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
+ *
+ *   https://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.accumulo.server.util.checkCommand;
+
+import org.apache.accumulo.server.util.Admin;
+
+public interface CheckRunner {
+
+  /**
+   * Runs the check
+   *
+   * @return the {@link Admin.CheckCommand.CheckStatus} resulting from running 
the check
+   */
+  Admin.CheckCommand.CheckStatus runCheck();
+
+  /**
+   *
+   * @return the check that this check runner runs
+   */
+  Admin.CheckCommand.Check getCheck();
+
+}
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/checkCommand/MetadataTableCheckRunner.java
 
b/server/base/src/main/java/org/apache/accumulo/server/util/checkCommand/MetadataTableCheckRunner.java
new file mode 100644
index 0000000000..aeed7adc23
--- /dev/null
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/util/checkCommand/MetadataTableCheckRunner.java
@@ -0,0 +1,40 @@
+/*
+ * 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
+ *
+ *   https://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.accumulo.server.util.checkCommand;
+
+import org.apache.accumulo.server.util.Admin;
+
+public class MetadataTableCheckRunner implements CheckRunner {
+  private static final Admin.CheckCommand.Check check = 
Admin.CheckCommand.Check.METADATA_TABLE;
+
+  @Override
+  public Admin.CheckCommand.CheckStatus runCheck() {
+    Admin.CheckCommand.CheckStatus status = Admin.CheckCommand.CheckStatus.OK;
+
+    System.out.println("Running check " + check);
+    // work
+    System.out.println("Check " + check + " completed with status " + status);
+    return status;
+  }
+
+  @Override
+  public Admin.CheckCommand.Check getCheck() {
+    return check;
+  }
+}
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/checkCommand/RootMetadataCheckRunner.java
 
b/server/base/src/main/java/org/apache/accumulo/server/util/checkCommand/RootMetadataCheckRunner.java
new file mode 100644
index 0000000000..bd4282246d
--- /dev/null
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/util/checkCommand/RootMetadataCheckRunner.java
@@ -0,0 +1,40 @@
+/*
+ * 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
+ *
+ *   https://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.accumulo.server.util.checkCommand;
+
+import org.apache.accumulo.server.util.Admin;
+
+public class RootMetadataCheckRunner implements CheckRunner {
+  private static final Admin.CheckCommand.Check check = 
Admin.CheckCommand.Check.ROOT_METADATA;
+
+  @Override
+  public Admin.CheckCommand.CheckStatus runCheck() {
+    Admin.CheckCommand.CheckStatus status = Admin.CheckCommand.CheckStatus.OK;
+
+    System.out.println("Running check " + check);
+    // work
+    System.out.println("Check " + check + " completed with status " + status);
+    return status;
+  }
+
+  @Override
+  public Admin.CheckCommand.Check getCheck() {
+    return check;
+  }
+}
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/checkCommand/RootTableCheckRunner.java
 
b/server/base/src/main/java/org/apache/accumulo/server/util/checkCommand/RootTableCheckRunner.java
new file mode 100644
index 0000000000..85558ed141
--- /dev/null
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/util/checkCommand/RootTableCheckRunner.java
@@ -0,0 +1,40 @@
+/*
+ * 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
+ *
+ *   https://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.accumulo.server.util.checkCommand;
+
+import org.apache.accumulo.server.util.Admin;
+
+public class RootTableCheckRunner implements CheckRunner {
+  private static final Admin.CheckCommand.Check check = 
Admin.CheckCommand.Check.ROOT_TABLE;
+
+  @Override
+  public Admin.CheckCommand.CheckStatus runCheck() {
+    Admin.CheckCommand.CheckStatus status = Admin.CheckCommand.CheckStatus.OK;
+
+    System.out.println("Running check " + check);
+    // work
+    System.out.println("Check " + check + " completed with status " + status);
+    return status;
+  }
+
+  @Override
+  public Admin.CheckCommand.Check getCheck() {
+    return check;
+  }
+}
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/checkCommand/SystemConfigCheckRunner.java
 
b/server/base/src/main/java/org/apache/accumulo/server/util/checkCommand/SystemConfigCheckRunner.java
new file mode 100644
index 0000000000..56e0b2b136
--- /dev/null
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/util/checkCommand/SystemConfigCheckRunner.java
@@ -0,0 +1,40 @@
+/*
+ * 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
+ *
+ *   https://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.accumulo.server.util.checkCommand;
+
+import org.apache.accumulo.server.util.Admin;
+
+public class SystemConfigCheckRunner implements CheckRunner {
+  private static final Admin.CheckCommand.Check check = 
Admin.CheckCommand.Check.SYSTEM_CONFIG;
+
+  @Override
+  public Admin.CheckCommand.CheckStatus runCheck() {
+    Admin.CheckCommand.CheckStatus status = Admin.CheckCommand.CheckStatus.OK;
+
+    System.out.println("Running check " + check);
+    // work
+    System.out.println("Check " + check + " completed with status " + status);
+    return status;
+  }
+
+  @Override
+  public Admin.CheckCommand.Check getCheck() {
+    return check;
+  }
+}
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/checkCommand/SystemFilesCheckRunner.java
 
b/server/base/src/main/java/org/apache/accumulo/server/util/checkCommand/SystemFilesCheckRunner.java
new file mode 100644
index 0000000000..284d170d9b
--- /dev/null
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/util/checkCommand/SystemFilesCheckRunner.java
@@ -0,0 +1,40 @@
+/*
+ * 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
+ *
+ *   https://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.accumulo.server.util.checkCommand;
+
+import org.apache.accumulo.server.util.Admin;
+
+public class SystemFilesCheckRunner implements CheckRunner {
+  private static final Admin.CheckCommand.Check check = 
Admin.CheckCommand.Check.SYSTEM_FILES;
+
+  @Override
+  public Admin.CheckCommand.CheckStatus runCheck() {
+    Admin.CheckCommand.CheckStatus status = Admin.CheckCommand.CheckStatus.OK;
+
+    System.out.println("Running check " + check);
+    // work
+    System.out.println("Check " + check + " completed with status " + status);
+    return status;
+  }
+
+  @Override
+  public Admin.CheckCommand.Check getCheck() {
+    return check;
+  }
+}
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/checkCommand/UserFilesCheckRunner.java
 
b/server/base/src/main/java/org/apache/accumulo/server/util/checkCommand/UserFilesCheckRunner.java
new file mode 100644
index 0000000000..088814e0f5
--- /dev/null
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/util/checkCommand/UserFilesCheckRunner.java
@@ -0,0 +1,42 @@
+/*
+ * 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
+ *
+ *   https://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.accumulo.server.util.checkCommand;
+
+import static org.apache.accumulo.server.util.Admin.CheckCommand.Check;
+
+import org.apache.accumulo.server.util.Admin;
+
+public class UserFilesCheckRunner implements CheckRunner {
+  private static final Check check = Check.USER_FILES;
+
+  @Override
+  public Admin.CheckCommand.CheckStatus runCheck() {
+    Admin.CheckCommand.CheckStatus status = Admin.CheckCommand.CheckStatus.OK;
+
+    System.out.println("Running check " + check);
+    // work
+    System.out.println("Check " + check + " completed with status " + status);
+    return status;
+  }
+
+  @Override
+  public Admin.CheckCommand.Check getCheck() {
+    return check;
+  }
+}
diff --git a/test/src/main/java/org/apache/accumulo/test/AdminCheckIT.java 
b/test/src/main/java/org/apache/accumulo/test/AdminCheckIT.java
new file mode 100644
index 0000000000..7ebefb047c
--- /dev/null
+++ b/test/src/main/java/org/apache/accumulo/test/AdminCheckIT.java
@@ -0,0 +1,411 @@
+/*
+ * 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
+ *
+ *   https://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.accumulo.test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.UncheckedIOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.function.Supplier;
+
+import org.apache.accumulo.server.cli.ServerUtilOpts;
+import org.apache.accumulo.server.util.Admin;
+import org.apache.accumulo.server.util.checkCommand.CheckRunner;
+import org.apache.accumulo.test.functional.ConfigurableMacBase;
+import org.easymock.EasyMock;
+import org.easymock.IAnswer;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import com.beust.jcommander.JCommander;
+
+public class AdminCheckIT extends ConfigurableMacBase {
+  private static final PrintStream ORIGINAL_OUT = System.out;
+
+  @AfterEach
+  public void assertCorrectPostTestState() {
+    // ensure that the output after the test is System.out
+    assertEquals(ORIGINAL_OUT, System.out);
+  }
+
+  /*
+   * The following tests test the expected outputs and functionality of the 
admin check command
+   * (e.g., are the correct checks run, dependencies run before the actual 
check, run in the correct
+   * order, etc.) without actually testing the correct functionality of the 
checks
+   */
+
+  @Test
+  public void testAdminCheckList() throws Exception {
+    // verifies output of list command
+
+    var p = getCluster().exec(Admin.class, "check", "list");
+    assertEquals(0, p.getProcess().waitFor());
+    String out = p.readStdOut();
+
+    // Checks that the header is correct and that all checks are in the output
+    assertTrue(
+        out.contains("Check Name") && out.contains("Description") && 
out.contains("Depends on"));
+    List<Admin.CheckCommand.Check> checksSeen = new ArrayList<>();
+    Arrays.stream(out.split("\\s+")).forEach(word -> {
+      try {
+        checksSeen.add(Admin.CheckCommand.Check.valueOf(word));
+      } catch (IllegalArgumentException e) {
+        // skip
+      }
+    });
+    
assertTrue(checksSeen.containsAll(List.of(Admin.CheckCommand.Check.values())));
+  }
+
+  @Test
+  public void testAdminCheckListAndRunTogether() throws Exception {
+    // Tries to execute list and run together; should not work
+
+    var p = getCluster().exec(Admin.class, "check", "list", "run");
+    assertNotEquals(0, p.getProcess().waitFor());
+    p = getCluster().exec(Admin.class, "check", "run", "list");
+    assertNotEquals(0, p.getProcess().waitFor());
+    p = getCluster().exec(Admin.class, "check", "run",
+        Admin.CheckCommand.Check.SYSTEM_CONFIG.name(), "list");
+    assertNotEquals(0, p.getProcess().waitFor());
+    p = getCluster().exec(Admin.class, "check", "list",
+        Admin.CheckCommand.Check.SYSTEM_CONFIG.name(), "run");
+    assertNotEquals(0, p.getProcess().waitFor());
+  }
+
+  @Test
+  public void testAdminCheckListAndRunInvalidArgs() throws Exception {
+    // tests providing invalid args to check
+
+    // extra args to list
+    var p = getCluster().exec(Admin.class, "check", "list", "abc");
+    assertNotEquals(0, p.getProcess().waitFor());
+    assertTrue(p.readStdOut().contains("'list' does not expect any further 
arguments"));
+    p = getCluster().exec(Admin.class, "check", "list",
+        Admin.CheckCommand.Check.SYSTEM_CONFIG.name());
+    assertNotEquals(0, p.getProcess().waitFor());
+    assertTrue(p.readStdOut().contains("'list' does not expect any further 
arguments"));
+    p = getCluster().exec(Admin.class, "check", "list", "-p", "abc");
+    assertNotEquals(0, p.getProcess().waitFor());
+    assertTrue(p.readStdOut().contains("'list' does not expect any further 
arguments"));
+    // invalid check to run
+    p = getCluster().exec(Admin.class, "check", "run", "123");
+    assertNotEquals(0, p.getProcess().waitFor());
+    assertTrue(p.readStdOut().contains("IllegalArgumentException"));
+    // no provided pattern
+    p = getCluster().exec(Admin.class, "check", "run", "-p");
+    assertNotEquals(0, p.getProcess().waitFor());
+    assertTrue(p.readStdOut().contains("ParameterException"));
+    // no checks match pattern
+    p = getCluster().exec(Admin.class, "check", "run", "-p", "abc");
+    assertNotEquals(0, p.getProcess().waitFor());
+    assertTrue(p.readStdOut().contains("No checks matched the given pattern"));
+    // invalid pattern
+    p = getCluster().exec(Admin.class, "check", "run", "-p", "[abc");
+    assertNotEquals(0, p.getProcess().waitFor());
+    assertTrue(p.readStdOut().contains("PatternSyntaxException"));
+    // more than one arg provided to pattern
+    p = getCluster().exec(Admin.class, "check", "run", "-p", ".*files", 
".*files");
+    assertNotEquals(0, p.getProcess().waitFor());
+    assertTrue(p.readStdOut().contains("Expected one argument (the regex 
pattern)"));
+    // no list or run provided
+    p = getCluster().exec(Admin.class, "check");
+    assertNotEquals(0, p.getProcess().waitFor());
+    assertTrue(p.readStdOut().contains("Must use either 'list' or 'run'"));
+  }
+
+  @Test
+  public void testAdminCheckRunNoCheckFailures() {
+    // tests running the checks with none failing on run
+
+    boolean[] allChecksPass = new 
boolean[Admin.CheckCommand.Check.values().length];
+    Arrays.fill(allChecksPass, true);
+
+    // no checks specified: should run all
+    String out1 = executeCheckCommand(new String[] {"check", "run"}, 
allChecksPass);
+    // all checks specified: should run all
+    String[] allChecksArgs = new 
String[Admin.CheckCommand.Check.values().length + 2];
+    allChecksArgs[0] = "check";
+    allChecksArgs[1] = "run";
+    for (int i = 2; i < allChecksArgs.length; i++) {
+      allChecksArgs[i] = Admin.CheckCommand.Check.values()[i - 2].name();
+    }
+    String out2 = executeCheckCommand(allChecksArgs, allChecksPass);
+    // this pattern: should run all
+    String out3 =
+        executeCheckCommand(new String[] {"check", "run", "-p", 
"[A-Z]+_[A-Z]+"}, allChecksPass);
+    // run subset of checks
+    String out4 = executeCheckCommand(
+        new String[] {"check", "run", "ROOT_TABLE", "SYSTEM_FILES", 
"USER_FILES"}, allChecksPass);
+    // run same subset of checks but using a pattern to specify the checks 
(case shouldn't matter)
+    String out5 = executeCheckCommand(new String[] {"check", "run", "-p", 
"ROOT_TABLE|.*files"},
+        allChecksPass);
+
+    String expRunAllRunOrder =
+        "Running dummy check SYSTEM_CONFIG\nDummy check SYSTEM_CONFIG 
completed with status OK\n"
+            + "Running dummy check ROOT_METADATA\nDummy check ROOT_METADATA 
completed with status OK\n"
+            + "Running dummy check ROOT_TABLE\nDummy check ROOT_TABLE 
completed with status OK\n"
+            + "Running dummy check METADATA_TABLE\nDummy check METADATA_TABLE 
completed with status OK\n"
+            + "Running dummy check SYSTEM_FILES\nDummy check SYSTEM_FILES 
completed with status OK\n"
+            + "Running dummy check USER_FILES\nDummy check USER_FILES 
completed with status OK";
+    String expRunSubRunOrder =
+        "Running dummy check ROOT_TABLE\nDummy check ROOT_TABLE completed with 
status OK\n"
+            + "Running dummy check SYSTEM_FILES\nDummy check SYSTEM_FILES 
completed with status OK\n"
+            + "Running dummy check USER_FILES\nDummy check USER_FILES 
completed with status OK\n";
+    // The dashes at the beginning and end of the string marks the begging and 
end of the
+    // printed table allowing us to ensure the table only includes what is 
expected
+    String expRunAllStatusInfo = 
"-SYSTEM_CONFIG|OKROOT_METADATA|OKROOT_TABLE|OK"
+        + "METADATA_TABLE|OKSYSTEM_FILES|OKUSER_FILES|OK-";
+    String expRunSubStatusInfo = 
"-SYSTEM_CONFIG|FILTERED_OUTROOT_METADATA|FILTERED_OUT"
+        + 
"ROOT_TABLE|OKMETADATA_TABLE|FILTERED_OUTSYSTEM_FILES|OKUSER_FILES|OK-";
+
+    assertTrue(out1.contains(expRunAllRunOrder));
+    assertTrue(out2.contains(expRunAllRunOrder));
+    assertTrue(out3.contains(expRunAllRunOrder));
+    assertTrue(out4.contains(expRunSubRunOrder));
+    assertTrue(out5.contains(expRunSubRunOrder));
+
+    out1 = out1.replaceAll("\\s+", "");
+    out2 = out2.replaceAll("\\s+", "");
+    out3 = out3.replaceAll("\\s+", "");
+    out4 = out4.replaceAll("\\s+", "");
+    out5 = out5.replaceAll("\\s+", "");
+
+    assertTrue(out1.contains(expRunAllStatusInfo));
+    assertTrue(out2.contains(expRunAllStatusInfo));
+    assertTrue(out3.contains(expRunAllStatusInfo));
+    assertTrue(out4.contains(expRunSubStatusInfo));
+    assertTrue(out5.contains(expRunSubStatusInfo));
+  }
+
+  @Test
+  public void testAdminCheckRunWithCheckFailures() {
+    // tests running checks with some failing
+
+    boolean[] rootTableFails = new boolean[] {true, true, false, true, true, 
true};
+    boolean[] systemConfigFails = new boolean[] {false, true, true, true, 
true, true};
+    boolean[] userFilesAndMetadataTableFails = new boolean[] {true, true, 
true, false, true, false};
+
+    // run all checks with ROOT_TABLE failing: only SYSTEM_CONFIG and 
ROOT_METADATA should pass
+    // the rest should be filtered out as skipped due to dependency failure
+    String out1 = executeCheckCommand(new String[] {"check", "run"}, 
rootTableFails);
+    // run all checks with SYSTEM_CONFIG failing: only SYSTEM_CONFIG should 
run and fail
+    // the rest should be filtered out as skipped due to dependency failure
+    String out2 = executeCheckCommand(new String[] {"check", "run"}, 
systemConfigFails);
+    // run subset of checks: SYSTEM_CONFIG, ROOT_TABLE, USER_FILES with 
USER_FILES and
+    // METADATA_TABLE failing
+    // should successfully run SYSTEM_CONFIG, ROOT_TABLE, fail to run 
USER_FILES and
+    // filter out the rest
+    String out3 = executeCheckCommand(
+        new String[] {"check", "run", "SYSTEM_CONFIG", "ROOT_TABLE", 
"USER_FILES"},
+        userFilesAndMetadataTableFails);
+    // run same subset but specified using pattern
+    String out4 = executeCheckCommand(
+        new String[] {"check", "run", "-p", 
"SYSTEM_CONFIG|ROOT_TABLE|USER_FILES"},
+        userFilesAndMetadataTableFails);
+
+    String expRunOrder1 =
+        "Running dummy check SYSTEM_CONFIG\nDummy check SYSTEM_CONFIG 
completed with status OK\n"
+            + "Running dummy check ROOT_METADATA\nDummy check ROOT_METADATA 
completed with status OK\n"
+            + "Running dummy check ROOT_TABLE\nDummy check ROOT_TABLE 
completed with status FAILED";
+    String expRunOrder2 =
+        "Running dummy check SYSTEM_CONFIG\nDummy check SYSTEM_CONFIG 
completed with status FAILED\n";
+    String expRunOrder3And4 =
+        "Running dummy check SYSTEM_CONFIG\nDummy check SYSTEM_CONFIG 
completed with status OK\n"
+            + "Running dummy check ROOT_TABLE\nDummy check ROOT_TABLE 
completed with status OK\n"
+            + "Running dummy check USER_FILES\nDummy check USER_FILES 
completed with status FAILED";
+
+    assertTrue(out1.contains(expRunOrder1));
+    assertTrue(out2.contains(expRunOrder2));
+    assertTrue(out3.contains(expRunOrder3And4));
+    assertTrue(out4.contains(expRunOrder3And4));
+
+    out1 = out1.replaceAll("\\s+", "");
+    out2 = out2.replaceAll("\\s+", "");
+    out3 = out3.replaceAll("\\s+", "");
+    out4 = out4.replaceAll("\\s+", "");
+
+    String expStatusInfo1 = 
"-SYSTEM_CONFIG|OKROOT_METADATA|OKROOT_TABLE|FAILED"
+        + 
"METADATA_TABLE|SKIPPED_DEPENDENCY_FAILEDSYSTEM_FILES|SKIPPED_DEPENDENCY_FAILED"
+        + "USER_FILES|SKIPPED_DEPENDENCY_FAILED-";
+    String expStatusInfo2 = 
"-SYSTEM_CONFIG|FAILEDROOT_METADATA|SKIPPED_DEPENDENCY_FAILED"
+        + 
"ROOT_TABLE|SKIPPED_DEPENDENCY_FAILEDMETADATA_TABLE|SKIPPED_DEPENDENCY_FAILED"
+        + 
"SYSTEM_FILES|SKIPPED_DEPENDENCY_FAILEDUSER_FILES|SKIPPED_DEPENDENCY_FAILED-";
+    String expStatusInfo3And4 = 
"-SYSTEM_CONFIG|OKROOT_METADATA|FILTERED_OUTROOT_TABLE|OK"
+        + 
"METADATA_TABLE|FILTERED_OUTSYSTEM_FILES|FILTERED_OUTUSER_FILES|FAILED";
+
+    assertTrue(out1.contains(expStatusInfo1));
+    assertTrue(out2.contains(expStatusInfo2));
+    assertTrue(out3.contains(expStatusInfo3And4));
+    assertTrue(out4.contains(expStatusInfo3And4));
+  }
+
+  private String executeCheckCommand(String[] checkCmdArgs, boolean[] 
checksPass) {
+    String output;
+    Admin admin = createMockAdmin(checksPass);
+
+    try (ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+        PrintStream printStream = new PrintStream(outStream)) {
+      System.setOut(printStream);
+      admin.execute(checkCmdArgs);
+      output = outStream.toString();
+    } catch (IOException e) {
+      throw new UncheckedIOException(e);
+    } finally {
+      EasyMock.verify(admin);
+      System.setOut(ORIGINAL_OUT);
+    }
+
+    return output;
+  }
+
+  private Admin createMockAdmin(boolean[] checksPass) {
+    // mocking admin.execute() to just execute "check" with our dummy check 
command
+    Admin admin = EasyMock.createMock(Admin.class);
+    admin.execute(EasyMock.anyObject(String[].class));
+    EasyMock.expectLastCall().andAnswer((IAnswer<Void>) () -> {
+      String[] args = EasyMock.getCurrentArgument(0);
+      ServerUtilOpts opts = new ServerUtilOpts();
+      JCommander cl = new JCommander(opts);
+      Admin.CheckCommand dummyCheckCommand = new DummyCheckCommand(checksPass);
+      cl.addCommand("check", dummyCheckCommand);
+      cl.parse(args);
+      Admin.executeCheckCommand(getServerContext(), dummyCheckCommand);
+      return null;
+    });
+    EasyMock.replay(admin);
+    return admin;
+  }
+
+  static abstract class DummyCheckRunner implements CheckRunner {
+    private final boolean passes;
+
+    public DummyCheckRunner(boolean passes) {
+      this.passes = passes;
+    }
+
+    @Override
+    public Admin.CheckCommand.CheckStatus runCheck() {
+      Admin.CheckCommand.CheckStatus status =
+          passes ? Admin.CheckCommand.CheckStatus.OK : 
Admin.CheckCommand.CheckStatus.FAILED;
+
+      System.out.println("Running dummy check " + getCheck());
+      // no work to perform in the dummy check runner
+      System.out.println("Dummy check " + getCheck() + " completed with status 
" + status);
+      return status;
+    }
+  }
+
+  static class DummySystemConfigCheckRunner extends DummyCheckRunner {
+    public DummySystemConfigCheckRunner(boolean passes) {
+      super(passes);
+    }
+
+    @Override
+    public Admin.CheckCommand.Check getCheck() {
+      return Admin.CheckCommand.Check.SYSTEM_CONFIG;
+    }
+  }
+
+  static class DummyRootMetadataCheckRunner extends DummyCheckRunner {
+    public DummyRootMetadataCheckRunner(boolean passes) {
+      super(passes);
+    }
+
+    @Override
+    public Admin.CheckCommand.Check getCheck() {
+      return Admin.CheckCommand.Check.ROOT_METADATA;
+    }
+  }
+
+  static class DummyRootTableCheckRunner extends DummyCheckRunner {
+    public DummyRootTableCheckRunner(boolean passes) {
+      super(passes);
+    }
+
+    @Override
+    public Admin.CheckCommand.Check getCheck() {
+      return Admin.CheckCommand.Check.ROOT_TABLE;
+    }
+  }
+
+  static class DummyMetadataTableCheckRunner extends DummyCheckRunner {
+    public DummyMetadataTableCheckRunner(boolean passes) {
+      super(passes);
+    }
+
+    @Override
+    public Admin.CheckCommand.Check getCheck() {
+      return Admin.CheckCommand.Check.METADATA_TABLE;
+    }
+  }
+
+  static class DummySystemFilesCheckRunner extends DummyCheckRunner {
+    public DummySystemFilesCheckRunner(boolean passes) {
+      super(passes);
+    }
+
+    @Override
+    public Admin.CheckCommand.Check getCheck() {
+      return Admin.CheckCommand.Check.SYSTEM_FILES;
+    }
+  }
+
+  static class DummyUserFilesCheckRunner extends DummyCheckRunner {
+    public DummyUserFilesCheckRunner(boolean passes) {
+      super(passes);
+    }
+
+    @Override
+    public Admin.CheckCommand.Check getCheck() {
+      return Admin.CheckCommand.Check.USER_FILES;
+    }
+  }
+
+  static class DummyCheckCommand extends Admin.CheckCommand {
+    final Map<Check,Supplier<CheckRunner>> checkRunners;
+
+    public DummyCheckCommand(boolean[] checksPass) {
+      this.checkRunners = new TreeMap<>();
+      this.checkRunners.put(Check.SYSTEM_CONFIG,
+          () -> new DummySystemConfigCheckRunner(checksPass[0]));
+      this.checkRunners.put(Check.ROOT_METADATA,
+          () -> new DummyRootMetadataCheckRunner(checksPass[1]));
+      this.checkRunners.put(Check.ROOT_TABLE, () -> new 
DummyRootTableCheckRunner(checksPass[2]));
+      this.checkRunners.put(Check.METADATA_TABLE,
+          () -> new DummyMetadataTableCheckRunner(checksPass[3]));
+      this.checkRunners.put(Check.SYSTEM_FILES,
+          () -> new DummySystemFilesCheckRunner(checksPass[4]));
+      this.checkRunners.put(Check.USER_FILES, () -> new 
DummyUserFilesCheckRunner(checksPass[5]));
+    }
+
+    @Override
+    public CheckRunner getCheckRunner(Check check) {
+      return checkRunners.get(check).get();
+    }
+  }
+}


Reply via email to