Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions assemble/bin/accumulo-service
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -111,7 +111,7 @@ function start_service() {
rotate_log "$outfile"
rotate_log "$errfile"

nohup "${bin}/accumulo" "$service_type" "$@" >"$outfile" 2>"$errfile" </dev/null &
nohup "${bin}/accumulo" "process" "$service_type" "$@" >"$outfile" 2>"$errfile" </dev/null &
echo "$!" >"${pid_file}"

done
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,6 @@ private ProcessInfo _exec(Class<?> clazz, List<String> 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);
Expand Down
101 changes: 60 additions & 41 deletions start/src/main/java/org/apache/accumulo/start/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -37,7 +39,7 @@ public class Main {

private static final Logger log = LoggerFactory.getLogger(Main.class);
private static ClassLoader classLoader;
private static Map<String,KeywordExecutable> servicesMap;
private static Map<UsageGroup,Map<String,KeywordExecutable>> servicesMap;

public static void main(final String[] args) throws Exception {
final ClassLoader loader = getClassLoader();
Expand All @@ -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));
}
}

}
Expand Down Expand Up @@ -157,61 +166,71 @@ private static void die(final Throwable t, String msg) {
System.exit(1);
}

public static void printCommands(TreeSet<KeywordExecutable> 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<KeywordExecutable> executables =
new TreeSet<>(Comparator.comparing(KeywordExecutable::keyword));
executables.addAll(getExecutables(getClassLoader()).values());

System.out.println("\nUsage: accumulo <command> [--help] (<argument> ...)\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"
+ " <main class> args Runs Java <main class> 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 (<argument> ...)");
System.out.println(" accumulo className (<argument> ...)");
System.out.println(" accumulo <group> <command> [--help] (<argument> ...)\n\n");

Map<UsageGroup,Map<String,KeywordExecutable>> exectuables = getExecutables(getClassLoader());
List<UsageGroup> 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<String,KeywordExecutable> getExecutables(final ClassLoader cl) {
public static synchronized Map<UsageGroup,Map<String,KeywordExecutable>>
getExecutables(final ClassLoader cl) {
if (servicesMap == null) {
servicesMap = checkDuplicates(ServiceLoader.load(KeywordExecutable.class, cl));
}
return servicesMap;
}

public static Map<String,KeywordExecutable>
private record BanKey(UsageGroup group, String keyword) implements Comparable<BanKey> {
@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<UsageGroup,Map<String,KeywordExecutable>>
checkDuplicates(final Iterable<? extends KeywordExecutable> services) {
TreeSet<String> banList = new TreeSet<>();
TreeMap<String,KeywordExecutable> results = new TreeMap<>();
TreeSet<BanKey> banList = new TreeSet<>();
EnumMap<UsageGroup,Map<String,KeywordExecutable>> 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) {
Expand Down
142 changes: 90 additions & 52 deletions test/src/main/java/org/apache/accumulo/test/start/KeywordStartIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -68,6 +69,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;
Expand All @@ -91,15 +93,17 @@ 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<String,KeywordExecutable> getKeywordExecutables() {
private Map<UsageGroup,Map<String,KeywordExecutable>> getKeywordExecutables() {
var all = Main.getExecutables(ClassLoader.getSystemClassLoader());
assumeTrue(!all.isEmpty());
return all;
}

@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
Expand All @@ -110,14 +114,30 @@ public void testCheckDuplicates() {
NoOp three = new NoOp("three");
List<NoOp> services = Arrays.asList(one, three, two, two, three, three, anotherOne);
assertEquals(7, services.size());
Map<String,KeywordExecutable> 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<UsageGroup,Map<String,KeywordExecutable>> 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<? extends KeywordExecutable> clazz) implements Comparable<CommandInfo> {

@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
Expand All @@ -126,48 +146,59 @@ public void testCheckDuplicates() {
@Test
public void testExpectedClasses() {
assumeTrue(Files.exists(Path.of(System.getProperty("user.dir")).resolve("src")));
TreeMap<String,Class<? extends KeywordExecutable>> expectSet = new TreeMap<>();
expectSet.put("admin", Admin.class);
expectSet.put("check-compaction-config", CheckCompactionConfig.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<CommandInfo> 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-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<UsageGroup,Map<String,KeywordExecutable>> actualExecutables =
new TreeMap<>(getKeywordExecutables());
SortedSet<CommandInfo> actualSet = new TreeSet<>();
actualExecutables.entrySet().forEach((e) -> {
e.getValue().entrySet().forEach((e2) -> {
actualSet.add(new CommandInfo(e.getKey(), e2.getKey(), e2.getValue().getClass()));
});
});

Iterator<CommandInfo> expectIter = expectSet.iterator();
Iterator<CommandInfo> actualIter = actualSet.iterator();

Iterator<Entry<String,Class<? extends KeywordExecutable>>> expectIter =
expectSet.entrySet().iterator();
TreeMap<String,KeywordExecutable> actualSet = new TreeMap<>(getKeywordExecutables());
Iterator<Entry<String,KeywordExecutable>> actualIter = actualSet.entrySet().iterator();
Entry<String,Class<? extends KeywordExecutable>> expected;
Entry<String,KeywordExecutable> 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) {
Expand Down Expand Up @@ -223,16 +254,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<UsageGroup,Map<String,KeywordExecutable>> actualExecutables =
new TreeMap<>(getKeywordExecutables());
Set<Class<? extends KeywordExecutable>> 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!"));
}

Expand Down