From cb6ef113342cdd2b110e034890d01b56a83ad72b Mon Sep 17 00:00:00 2001 From: Dave Marion Date: Wed, 4 Feb 2026 19:19:47 +0000 Subject: [PATCH] Modified accumulo script to use group name in command syntax Modified command syntax from `accumulo ` to `accumulo `. This work will be leveraged in a future change to decompose the `admin` command into seperate KeywordExecutable instances in an `admin` group. Related to #6086 --- assemble/bin/accumulo-service | 6 +- .../MiniAccumuloClusterImpl.java | 1 - .../java/org/apache/accumulo/start/Main.java | 101 +++++++----- .../accumulo/test/start/KeywordStartIT.java | 145 +++++++++++------- 4 files changed, 155 insertions(+), 98 deletions(-) diff --git a/assemble/bin/accumulo-service b/assemble/bin/accumulo-service index b887160ecf1..af4aa83dea8 100755 --- a/assemble/bin/accumulo-service +++ b/assemble/bin/accumulo-service @@ -27,8 +27,8 @@ Services: monitor Accumulo monitor manager Accumulo manager tserver Accumulo tserver - compactor Accumulo compactor (experimental) - sserver Accumulo scan server (experimental) + compactor Accumulo compactor + sserver Accumulo scan server Commands: start Starts service(s) @@ -111,7 +111,7 @@ function start_service() { rotate_log "$outfile" rotate_log "$errfile" - nohup "${bin}/accumulo" "$service_type" "$@" >"$outfile" 2>"$errfile" "$outfile" 2>"$errfile" "${pid_file}" done diff --git a/minicluster/src/main/java/org/apache/accumulo/miniclusterImpl/MiniAccumuloClusterImpl.java b/minicluster/src/main/java/org/apache/accumulo/miniclusterImpl/MiniAccumuloClusterImpl.java index d1977799a7d..f9703f721bc 100644 --- a/minicluster/src/main/java/org/apache/accumulo/miniclusterImpl/MiniAccumuloClusterImpl.java +++ b/minicluster/src/main/java/org/apache/accumulo/miniclusterImpl/MiniAccumuloClusterImpl.java @@ -431,7 +431,6 @@ private ProcessInfo _exec(Class clazz, List extraJvmOpts, String... a File stdErr = logDir.resolve(clazz.getSimpleName() + "_" + hashcode + ".err").toFile(); Process process = builder.redirectError(stdErr).redirectOutput(stdOut).start(); - cleanup.add(process); return new ProcessInfo(process, stdOut); diff --git a/start/src/main/java/org/apache/accumulo/start/Main.java b/start/src/main/java/org/apache/accumulo/start/Main.java index 3f6eea7992c..d45efd5e57f 100644 --- a/start/src/main/java/org/apache/accumulo/start/Main.java +++ b/start/src/main/java/org/apache/accumulo/start/Main.java @@ -21,8 +21,10 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.util.Arrays; import java.util.Collections; -import java.util.Comparator; +import java.util.EnumMap; +import java.util.List; import java.util.Map; import java.util.ServiceLoader; import java.util.TreeMap; @@ -37,7 +39,7 @@ public class Main { private static final Logger log = LoggerFactory.getLogger(Main.class); private static ClassLoader classLoader; - private static Map servicesMap; + private static Map> servicesMap; public static void main(final String[] args) throws Exception { final ClassLoader loader = getClassLoader(); @@ -51,14 +53,21 @@ public static void main(final String[] args) throws Exception { return; } - // determine whether a keyword was used or a class name, and execute it with the remaining - // args - String keywordOrClassName = args[0]; - KeywordExecutable keywordExec = getExecutables(loader).get(keywordOrClassName); - if (keywordExec != null) { - execKeyword(keywordExec, stripArgs(args, 1)); + if (args.length == 1) { + execMainClassName(args[0], new String[] {}); } else { - execMainClassName(keywordOrClassName, stripArgs(args, 1)); + String arg1 = args[0]; + String arg2 = args[1]; + KeywordExecutable keywordExec = null; + try { + UsageGroup group = UsageGroup.valueOf(arg1.toUpperCase()); + keywordExec = getExecutables(loader).get(group).get(arg2); + } catch (IllegalArgumentException e) {} + if (keywordExec != null) { + execKeyword(keywordExec, stripArgs(args, 2)); + } else { + execMainClassName(arg1, stripArgs(args, 1)); + } } } @@ -157,61 +166,71 @@ private static void die(final Throwable t, String msg) { System.exit(1); } - public static void printCommands(TreeSet set, UsageGroup group) { - set.stream().filter(e -> e.usageGroup() == group) - .forEach(ke -> System.out.printf(" %-30s %s\n", ke.usage(), ke.description())); - } - public static void printUsage() { - TreeSet executables = - new TreeSet<>(Comparator.comparing(KeywordExecutable::keyword)); - executables.addAll(getExecutables(getClassLoader()).values()); - - System.out.println("\nUsage: accumulo [--help] ( ...)\n\n" - + " --help Prints usage for specified command"); - System.out.println("\nCore Commands:"); - printCommands(executables, UsageGroup.CORE); - System.out.println(" jshell Runs JShell for Accumulo\n" - + " classpath Prints Accumulo classpath\n" - + "
args Runs Java
located on Accumulo classpath"); - - System.out.println("\nProcess Commands:"); - printCommands(executables, UsageGroup.PROCESS); - - System.out.println("\nOther Commands:"); - printCommands(executables, UsageGroup.OTHER); + System.out.println("\nUsage one of:"); + System.out.println(" accumulo --help"); + System.out.println(" accumulo classpath"); + System.out.println(" accumulo jshell ( ...)"); + System.out.println(" accumulo className ( ...)"); + System.out.println(" accumulo [--help] ( ...)\n\n"); + + Map> exectuables = getExecutables(getClassLoader()); + List groups = Arrays.asList(UsageGroup.values()); + Collections.sort(groups); + groups.forEach(g -> { + System.out.println("\n" + g.name() + " Group Commands:"); + exectuables.get(g).values() + .forEach(ke -> System.out.printf(" %-30s %s\n", ke.usage(), ke.description())); + }); System.out.println(); } - public static synchronized Map getExecutables(final ClassLoader cl) { + public static synchronized Map> + getExecutables(final ClassLoader cl) { if (servicesMap == null) { servicesMap = checkDuplicates(ServiceLoader.load(KeywordExecutable.class, cl)); } return servicesMap; } - public static Map + private record BanKey(UsageGroup group, String keyword) implements Comparable { + @Override + public int compareTo(BanKey o) { + int result = this.group.compareTo(o.group); + if (result == 0) { + result = this.keyword.compareTo(o.keyword); + } + return result; + } + }; + + public static Map> checkDuplicates(final Iterable services) { - TreeSet banList = new TreeSet<>(); - TreeMap results = new TreeMap<>(); + TreeSet banList = new TreeSet<>(); + EnumMap> results = new EnumMap<>(UsageGroup.class); + for (UsageGroup ug : UsageGroup.values()) { + results.put(ug, new TreeMap<>()); + } for (KeywordExecutable service : services) { + UsageGroup group = service.usageGroup(); String keyword = service.keyword(); - if (banList.contains(keyword)) { + BanKey bk = new BanKey(group, keyword); + if (banList.contains(bk)) { // subsequent times a duplicate is found, just warn and exclude it warnDuplicate(service); - } else if (results.containsKey(keyword)) { + } else if (results.get(group).containsKey(keyword)) { // the first time a duplicate is found, banList it and warn - banList.add(keyword); - warnDuplicate(results.remove(keyword)); + banList.add(bk); + warnDuplicate(results.get(group).remove(keyword)); warnDuplicate(service); } else { // first observance of this keyword, so just add it to the list - results.put(service.keyword(), service); + results.get(group).put(service.keyword(), service); } } - return Collections.unmodifiableSortedMap(results); + return Collections.unmodifiableMap(results); } private static void warnDuplicate(final KeywordExecutable service) { diff --git a/test/src/main/java/org/apache/accumulo/test/start/KeywordStartIT.java b/test/src/main/java/org/apache/accumulo/test/start/KeywordStartIT.java index a917ea9ee3f..06e6bafb4e7 100644 --- a/test/src/main/java/org/apache/accumulo/test/start/KeywordStartIT.java +++ b/test/src/main/java/org/apache/accumulo/test/start/KeywordStartIT.java @@ -18,7 +18,6 @@ */ package org.apache.accumulo.test.start; -import static java.util.stream.Collectors.toSet; import static org.apache.accumulo.harness.AccumuloITBase.SUNNY_DAY; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -35,8 +34,10 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Map.Entry; +import java.util.Set; +import java.util.SortedSet; import java.util.TreeMap; +import java.util.TreeSet; import org.apache.accumulo.compactor.CompactorExecutable; import org.apache.accumulo.core.file.rfile.GenerateSplits; @@ -69,6 +70,7 @@ import org.apache.accumulo.shell.Shell; import org.apache.accumulo.start.Main; import org.apache.accumulo.start.spi.KeywordExecutable; +import org.apache.accumulo.start.spi.KeywordExecutable.UsageGroup; import org.apache.accumulo.tserver.ScanServerExecutable; import org.apache.accumulo.tserver.TServerExecutable; import org.apache.accumulo.tserver.TabletServer; @@ -92,7 +94,7 @@ public class KeywordStartIT { * There may be other ways to run annotation processors in your IDE, so this may not be necessary, * depending on your IDE and its configuration. */ - private Map getKeywordExecutables() { + private Map> getKeywordExecutables() { var all = Main.getExecutables(ClassLoader.getSystemClassLoader()); assumeTrue(!all.isEmpty()); return all; @@ -100,7 +102,9 @@ private Map getKeywordExecutables() { @Test public void testKeywordsMatch() { - getKeywordExecutables().forEach((k, v) -> assertEquals(k, v.keyword())); + getKeywordExecutables().forEach((k, v) -> { + v.forEach((k2, v2) -> assertEquals(k2, v2.keyword())); + }); } @Test @@ -111,14 +115,30 @@ public void testCheckDuplicates() { NoOp three = new NoOp("three"); List services = Arrays.asList(one, three, two, two, three, three, anotherOne); assertEquals(7, services.size()); - Map results = Main.checkDuplicates(services); - assertTrue(results.containsKey(one.keyword())); - assertTrue(results.containsKey(anotherOne.keyword())); - assertFalse(results.containsKey(two.keyword())); - assertFalse(results.containsKey(three.keyword())); - assertEquals(2, results.size()); + Map> results = Main.checkDuplicates(services); + assertTrue(results.get(UsageGroup.OTHER).containsKey(one.keyword())); + assertTrue(results.get(UsageGroup.OTHER).containsKey(anotherOne.keyword())); + assertFalse(results.get(UsageGroup.OTHER).containsKey(two.keyword())); + assertFalse(results.get(UsageGroup.OTHER).containsKey(three.keyword())); + assertEquals(2, results.get(UsageGroup.OTHER).size()); } + private record CommandInfo(UsageGroup group, String keyword, + Class clazz) implements Comparable { + + @Override + public int compareTo(CommandInfo o) { + int result = this.group.compareTo(o.group); + if (result == 0) { + result = this.keyword.compareTo(o.keyword); + if (result == 0) { + result = this.clazz.getName().compareTo(o.clazz.getName()); + } + } + return result; + } + }; + /** * This test guards against accidental renaming or incorrect naming of the keyword used to * identify the service. The keyword is used to access the commands via the command line, so @@ -127,49 +147,61 @@ public void testCheckDuplicates() { @Test public void testExpectedClasses() { assumeTrue(Files.exists(Path.of(System.getProperty("user.dir")).resolve("src"))); - TreeMap> expectSet = new TreeMap<>(); - expectSet.put("admin", Admin.class); - expectSet.put("check-compaction-config", CheckCompactionConfig.class); - expectSet.put("check-server-config", CheckServerConfig.class); - expectSet.put("check-accumulo-properties", CheckAccumuloProperties.class); - expectSet.put("compactor", CompactorExecutable.class); - expectSet.put("create-empty", CreateEmpty.class); - expectSet.put("create-token", CreateToken.class); - expectSet.put("dump-zoo", DumpZookeeper.class); - expectSet.put("ec-admin", ECAdmin.class); - expectSet.put("gc", GCExecutable.class); - expectSet.put("generate-splits", GenerateSplits.class); - expectSet.put("help", Help.class); - expectSet.put("info", Info.class); - expectSet.put("init", Initialize.class); - expectSet.put("login-info", LoginProperties.class); - expectSet.put("manager", ManagerExecutable.class); - expectSet.put("minicluster", MiniClusterExecutable.class); - expectSet.put("monitor", MonitorExecutable.class); - expectSet.put("rfile-info", PrintInfo.class); - expectSet.put("shell", Shell.class); - expectSet.put("split-large", SplitLarge.class); - expectSet.put("sserver", ScanServerExecutable.class); - expectSet.put("tserver", TServerExecutable.class); - expectSet.put("upgrade", UpgradeUtil.class); - expectSet.put("version", Version.class); - expectSet.put("wal-info", LogReader.class); - expectSet.put("zoo-info-viewer", ZooInfoViewer.class); - expectSet.put("zoo-prop-editor", ZooPropEditor.class); - expectSet.put("zoo-zap", ZooZap.class); - expectSet.put("zookeeper", ZooKeeperMain.class); + SortedSet expectSet = new TreeSet<>(); + expectSet.add(new CommandInfo(UsageGroup.CORE, "admin", Admin.class)); + expectSet.add( + new CommandInfo(UsageGroup.OTHER, "check-compaction-config", CheckCompactionConfig.class)); + expectSet + .add(new CommandInfo(UsageGroup.OTHER, "check-server-config", CheckServerConfig.class)); + expectSet.add(new CommandInfo(UsageGroup.OTHER, "check-accumulo-properties", + CheckAccumuloProperties.class)); + expectSet.add(new CommandInfo(UsageGroup.PROCESS, "compactor", CompactorExecutable.class)); + expectSet.add(new CommandInfo(UsageGroup.OTHER, "create-empty", CreateEmpty.class)); + expectSet.add(new CommandInfo(UsageGroup.OTHER, "create-token", CreateToken.class)); + expectSet.add(new CommandInfo(UsageGroup.OTHER, "dump-zoo", DumpZookeeper.class)); + expectSet.add(new CommandInfo(UsageGroup.CORE, "ec-admin", ECAdmin.class)); + expectSet.add(new CommandInfo(UsageGroup.PROCESS, "gc", GCExecutable.class)); + expectSet.add(new CommandInfo(UsageGroup.OTHER, "generate-splits", GenerateSplits.class)); + expectSet.add(new CommandInfo(UsageGroup.CORE, "help", Help.class)); + expectSet.add(new CommandInfo(UsageGroup.CORE, "info", Info.class)); + expectSet.add(new CommandInfo(UsageGroup.CORE, "init", Initialize.class)); + expectSet.add(new CommandInfo(UsageGroup.OTHER, "login-info", LoginProperties.class)); + expectSet.add(new CommandInfo(UsageGroup.PROCESS, "manager", ManagerExecutable.class)); + expectSet.add(new CommandInfo(UsageGroup.PROCESS, "minicluster", MiniClusterExecutable.class)); + expectSet.add(new CommandInfo(UsageGroup.PROCESS, "monitor", MonitorExecutable.class)); + expectSet.add(new CommandInfo(UsageGroup.OTHER, "rfile-info", PrintInfo.class)); + expectSet.add(new CommandInfo(UsageGroup.CORE, "shell", Shell.class)); + expectSet.add(new CommandInfo(UsageGroup.OTHER, "split-large", SplitLarge.class)); + expectSet.add(new CommandInfo(UsageGroup.PROCESS, "sserver", ScanServerExecutable.class)); + expectSet.add(new CommandInfo(UsageGroup.PROCESS, "tserver", TServerExecutable.class)); + expectSet.add(new CommandInfo(UsageGroup.OTHER, "upgrade", UpgradeUtil.class)); + expectSet.add(new CommandInfo(UsageGroup.CORE, "version", Version.class)); + expectSet.add(new CommandInfo(UsageGroup.OTHER, "wal-info", LogReader.class)); + expectSet.add(new CommandInfo(UsageGroup.OTHER, "zoo-info-viewer", ZooInfoViewer.class)); + expectSet.add(new CommandInfo(UsageGroup.OTHER, "zoo-prop-editor", ZooPropEditor.class)); + expectSet.add(new CommandInfo(UsageGroup.OTHER, "zoo-zap", ZooZap.class)); + expectSet.add(new CommandInfo(UsageGroup.PROCESS, "zookeeper", ZooKeeperMain.class)); + + Map> actualExecutables = + new TreeMap<>(getKeywordExecutables()); + SortedSet actualSet = new TreeSet<>(); + actualExecutables.entrySet().forEach((e) -> { + e.getValue().entrySet().forEach((e2) -> { + actualSet.add(new CommandInfo(e.getKey(), e2.getKey(), e2.getValue().getClass())); + }); + }); + + Iterator expectIter = expectSet.iterator(); + Iterator actualIter = actualSet.iterator(); - Iterator>> expectIter = - expectSet.entrySet().iterator(); - TreeMap actualSet = new TreeMap<>(getKeywordExecutables()); - Iterator> actualIter = actualSet.entrySet().iterator(); - Entry> expected; - Entry actual; + CommandInfo expected; + CommandInfo actual; while (expectIter.hasNext() && actualIter.hasNext()) { expected = expectIter.next(); actual = actualIter.next(); - assertEquals(expected.getKey(), actual.getKey()); - assertEquals(expected.getValue(), actual.getValue().getClass()); + assertEquals(expected.group(), actual.group()); + assertEquals(expected.keyword(), actual.keyword()); + assertEquals(expected.clazz(), actual.clazz()); } boolean moreExpected = expectIter.hasNext(); if (moreExpected) { @@ -226,16 +258,23 @@ public void checkHasMain() { c -> assertTrue(hasMain(c), "Class " + c.getName() + " is missing a main method!")); // build a list of all classed that implement KeywordExecutable - var all = getKeywordExecutables().values().stream().map(Object::getClass).collect(toSet()); + Map> actualExecutables = + new TreeMap<>(getKeywordExecutables()); + Set> actualSet = new HashSet<>(); + actualExecutables.entrySet().forEach((e) -> { + e.getValue().entrySet().forEach((e2) -> { + actualSet.add(e2.getValue().getClass()); + }); + }); // remove the ones we already verified have a main method - assertTrue(all.removeAll(expectSet)); + assertTrue(actualSet.removeAll(expectSet)); // ensure there's still some left (there should be some that don't have a main method) - assertNotEquals(0, all.size()); + assertNotEquals(0, actualSet.size()); // for those remaining, make sure they *don't* have an unexpected main method - all.forEach( + actualSet.forEach( c -> assertFalse(hasMain(c), "Class " + c.getName() + " has an unexpected main method!")); }