| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed 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 com.android.tradefed.command; |
| |
| import com.android.ddmlib.Log.LogLevel; |
| import com.android.tradefed.clearcut.ClearcutClient; |
| import com.android.tradefed.clearcut.TerminateClearcutClient; |
| import com.android.tradefed.command.CommandRunner.ExitCode; |
| import com.android.tradefed.command.console.ConfigCompleter; |
| import com.android.tradefed.command.console.ConsoleReaderOutputStream; |
| import com.android.tradefed.config.ArgsOptionParser; |
| import com.android.tradefed.config.ConfigurationException; |
| import com.android.tradefed.config.ConfigurationFactory; |
| import com.android.tradefed.config.GlobalConfiguration; |
| import com.android.tradefed.config.IConfigurationFactory; |
| import com.android.tradefed.config.Option; |
| import com.android.tradefed.config.proxy.AutomatedReporters; |
| import com.android.tradefed.device.IDeviceManager; |
| import com.android.tradefed.invoker.InvocationContext; |
| import com.android.tradefed.invoker.TestInvocation; |
| import com.android.tradefed.log.LogRegistry; |
| import com.android.tradefed.log.LogUtil.CLog; |
| import com.android.tradefed.result.FailureDescription; |
| import com.android.tradefed.result.proto.FileProtoResultReporter; |
| import com.android.tradefed.result.proto.TestRecordProto.FailureStatus; |
| import com.android.tradefed.service.TradefedFeatureServer; |
| import com.android.tradefed.service.management.DeviceManagementGrpcServer; |
| import com.android.tradefed.service.management.TestInvocationManagementServer; |
| import com.android.tradefed.testtype.suite.TestSuiteInfo; |
| import com.android.tradefed.util.ArrayUtil; |
| import com.android.tradefed.util.FileUtil; |
| import com.android.tradefed.util.QuotationAwareTokenizer; |
| import com.android.tradefed.util.RegexTrie; |
| import com.android.tradefed.util.RunUtil; |
| import com.android.tradefed.util.StreamUtil; |
| import com.android.tradefed.util.TimeUtil; |
| import com.android.tradefed.util.VersionParser; |
| import com.android.tradefed.util.ZipUtil; |
| import com.android.tradefed.util.keystore.IKeyStoreFactory; |
| import com.android.tradefed.util.keystore.KeyStoreException; |
| |
| import com.google.common.annotations.VisibleForTesting; |
| |
| import org.jline.reader.EndOfFileException; |
| import org.jline.reader.LineReader; |
| import org.jline.reader.LineReaderBuilder; |
| import org.jline.reader.impl.history.DefaultHistory; |
| import org.jline.terminal.TerminalBuilder; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.PrintStream; |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.ListIterator; |
| import java.util.Map; |
| import java.util.TreeMap; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import java.util.regex.Pattern; |
| |
| import sun.misc.Signal; |
| import sun.misc.SignalHandler; |
| |
| /** |
| * Main TradeFederation console providing user with the interface to interact |
| * |
| * <p>Currently supports operations such as |
| * |
| * <ul> |
| * <li>add a command to test |
| * <li>list devices and their state |
| * <li>list invocations in progress |
| * <li>list commands in queue |
| * <li>dump invocation log to file/stdout |
| * <li>shutdown |
| * </ul> |
| */ |
| public class Console extends Thread { |
| |
| private static final String APPNAME = "Tradefed"; |
| private static final String CONSOLE_PROMPT = "\u001B[0;32mtf >\u001B[0;0m"; |
| |
| protected static final String HELP_PATTERN = "\\?|h|help"; |
| protected static final String LIST_PATTERN = "l(?:ist)?"; |
| protected static final String DUMP_PATTERN = "d(?:ump)?"; |
| protected static final String RUN_PATTERN = "r(?:un)?"; |
| protected static final String EXIT_PATTERN = "(?:q|exit)"; |
| protected static final String SET_PATTERN = "s(?:et)?"; |
| protected static final String INVOC_PATTERN = "i(?:nvocation)?"; |
| protected static final String VERSION_PATTERN = "version"; |
| protected static final String REMOVE_PATTERN = "remove"; |
| protected static final String DEBUG_PATTERN = "debug"; |
| protected static final String LIST_COMMANDS_PATTERN = "c(?:ommands)?"; |
| |
| protected static final String LINE_SEPARATOR = System.getProperty("line.separator"); |
| |
| private static ConsoleReaderOutputStream sConsoleStream = null; |
| |
| protected ICommandScheduler mScheduler; |
| protected IKeyStoreFactory mKeyStoreFactory; |
| protected LineReader mConsoleReader; |
| private RegexTrie<Runnable> mCommandTrie = new RegexTrie<Runnable>(); |
| private AtomicBoolean mShouldExit = new AtomicBoolean(false); |
| private List<String> mMainArgs = new ArrayList<String>(0); |
| private long mConsoleStartTime; |
| |
| /** A convenience type for <code>{@literal List<List<String>>}</code> */ |
| @SuppressWarnings("serial") |
| protected static class CaptureList extends LinkedList<List<String>> { |
| CaptureList() { |
| super(); |
| } |
| |
| CaptureList(Collection<? extends List<String>> c) { |
| super(c); |
| } |
| } |
| |
| /** A {@link Runnable} with a {@code run} method that can take an argument */ |
| protected abstract static class ArgRunnable<T> implements Runnable { |
| @Override |
| public void run() { |
| run(null); |
| } |
| |
| public abstract void run(T args); |
| } |
| |
| /** |
| * This is a sentinel class that will cause TF to shut down. This enables a user to get TF to |
| * shut down via the RegexTrie input handling mechanism. |
| */ |
| private class QuitRunnable extends ArgRunnable<CaptureList> { |
| |
| @Option( |
| name = "wait-for-commands", |
| shortName = 'c', |
| description = "only exit after all commands have executed ") |
| private boolean mExitOnEmpty = false; |
| |
| @Override |
| public void run(CaptureList args) { |
| try { |
| if (args.size() >= 2 && !args.get(1).isEmpty()) { |
| List<String> optionArgs = getFlatArgs(1, args); |
| ArgsOptionParser parser = new ArgsOptionParser(this); |
| if (mKeyStoreFactory != null) { |
| parser.setKeyStore(mKeyStoreFactory.createKeyStoreClient()); |
| } |
| parser.parse(optionArgs); |
| } |
| String exitMode = "invocations"; |
| if (mExitOnEmpty) { |
| exitMode = "commands"; |
| mScheduler.shutdownOnEmpty(); |
| } else { |
| mScheduler.shutdown(true); |
| } |
| printLine("Signalling command scheduler for shutdown."); |
| printLine( |
| String.format( |
| "TF will exit without warning when remaining %s complete.", |
| exitMode)); |
| } catch (ConfigurationException e) { |
| printLine(e.toString()); |
| } catch (KeyStoreException e) { |
| printLine(e.toString()); |
| } |
| } |
| } |
| |
| /** |
| * Like {@link QuitRunnable}, but attempts to harshly shut down current invocations by killing |
| * the adb connection |
| */ |
| private class ForceQuitRunnable extends QuitRunnable { |
| @Override |
| public void run(CaptureList args) { |
| mScheduler.shutdownHard(); |
| } |
| } |
| |
| /** |
| * Retrieve the {@link RegexTrie} that defines the console behavior. Exposed for unit testing. |
| */ |
| RegexTrie<Runnable> getCommandTrie() { |
| return mCommandTrie; |
| } |
| |
| /** |
| * Return a new LineReader, or {@code null} if an IOException occurs. Note that this function |
| * must be static so that we can run it before the superclass constructor. |
| */ |
| protected static LineReader getReader() { |
| try { |
| if (sConsoleStream == null) { |
| final LineReader reader = |
| LineReaderBuilder.builder() |
| .appName(APPNAME) |
| .terminal(TerminalBuilder.builder().system(true).dumb(true).build()) |
| .completer( |
| new ConfigCompleter( |
| ConfigurationFactory.getInstance().getConfigList())) |
| .history(new DefaultHistory()) |
| .build(); |
| sConsoleStream = new ConsoleReaderOutputStream(reader); |
| System.setOut(new PrintStream(sConsoleStream, true)); |
| } |
| return sConsoleStream.getConsoleReader(); |
| } catch (IOException e) { |
| System.err.format("Failed to initialize LineReader: %s\n", e.getMessage()); |
| return null; |
| } |
| } |
| |
| protected Console() { |
| this(getReader()); |
| } |
| |
| /** |
| * Create a {@link Console} with provided console reader. Also, set up console command handling. |
| * |
| * <p>Exposed for unit testing |
| */ |
| Console(LineReader reader) { |
| super("TfConsole"); |
| mConsoleStartTime = System.currentTimeMillis(); |
| mConsoleReader = reader; |
| |
| List<String> genericHelp = new LinkedList<String>(); |
| Map<String, String> commandHelp = new LinkedHashMap<String, String>(); |
| addDefaultCommands(mCommandTrie, genericHelp, commandHelp); |
| setCustomCommands(mCommandTrie, genericHelp, commandHelp); |
| generateHelpListings(mCommandTrie, genericHelp, commandHelp); |
| } |
| |
| void setCommandScheduler(ICommandScheduler scheduler) { |
| mScheduler = scheduler; |
| } |
| |
| void setKeyStoreFactory(IKeyStoreFactory factory) { |
| mKeyStoreFactory = factory; |
| } |
| |
| /** |
| * Register shutdown signals. |
| * |
| * <p>TSTP signal for quitting tradefed which waits all invocation finish. TERM signal for |
| * killing tradefed. We use TSTP and INT because these two signals are not used by JVM. |
| */ |
| void registerShutdownSignals() { |
| Signal.handle( |
| new Signal("TSTP"), |
| new SignalHandler() { |
| @Override |
| public void handle(Signal sig) { |
| CLog.logAndDisplay( |
| LogLevel.INFO, |
| String.format("Received signal %s. Quit.", sig.getName())); |
| new QuitRunnable().run(new CaptureList()); |
| } |
| }); |
| Signal.handle( |
| new Signal("TERM"), |
| new SignalHandler() { |
| @Override |
| public void handle(Signal sig) { |
| CLog.logAndDisplay( |
| LogLevel.INFO, |
| String.format("Received signal %s. Kill.", sig.getName())); |
| new ForceQuitRunnable().run(new CaptureList()); |
| } |
| }); |
| } |
| |
| /** |
| * A customization point that subclasses can use to alter which commands are available in the |
| * console. |
| * |
| * <p>Implementations should modify the {@code genericHelp} and {@code commandHelp} variables to |
| * document what functionality they may have added, modified, or removed. |
| * |
| * @param trie The {@link RegexTrie} to add the commands to |
| * @param genericHelp A {@link List} of lines to print when the user runs the "help" command |
| * with no arguments. |
| * @param commandHelp A {@link Map} containing documentation for any new commands that may have |
| * been added. The key is a regular expression to use as a key for {@link RegexTrie}. The |
| * value should be a String containing the help text to print for that command. |
| */ |
| protected void setCustomCommands( |
| RegexTrie<Runnable> trie, List<String> genericHelp, Map<String, String> commandHelp) { |
| // Meant to be overridden by subclasses |
| } |
| |
| /** |
| * Generate help listings based on the contents of {@code genericHelp} and {@code commandHelp}. |
| * |
| * @param trie The {@link RegexTrie} to add the commands to |
| * @param genericHelp A {@link List} of lines to print when the user runs the "help" command |
| * with no arguments. |
| * @param commandHelp A {@link Map} containing documentation for any new commands that may have |
| * been added. The key is a regular expression to use as a key for {@link RegexTrie}. The |
| * value should be a String containing the help text to print for that command. |
| */ |
| void generateHelpListings( |
| RegexTrie<Runnable> trie, List<String> genericHelp, Map<String, String> commandHelp) { |
| final String genHelpString = getGenericHelpString(genericHelp); |
| |
| final ArgRunnable<CaptureList> genericHelpRunnable = |
| new ArgRunnable<CaptureList>() { |
| @Override |
| public void run(CaptureList args) { |
| printLine(genHelpString); |
| } |
| }; |
| trie.put(genericHelpRunnable, HELP_PATTERN); |
| |
| StringBuilder allHelpBuilder = new StringBuilder(); |
| |
| // Add help entries for everything listed in the commandHelp map |
| for (Map.Entry<String, String> helpPair : commandHelp.entrySet()) { |
| final String key = helpPair.getKey(); |
| final String helpText = helpPair.getValue(); |
| |
| trie.put( |
| new Runnable() { |
| @Override |
| public void run() { |
| printLine(helpText); |
| } |
| }, |
| HELP_PATTERN, |
| key); |
| |
| allHelpBuilder.append(helpText); |
| allHelpBuilder.append(LINE_SEPARATOR); |
| } |
| |
| final String allHelpText = allHelpBuilder.toString(); |
| trie.put( |
| new Runnable() { |
| @Override |
| public void run() { |
| printLine(allHelpText); |
| } |
| }, |
| HELP_PATTERN, |
| "all"); |
| |
| // Add a generic "not found" help message for everything else |
| trie.put( |
| new ArgRunnable<CaptureList>() { |
| @Override |
| public void run(CaptureList args) { |
| // Command will be the only capture in the second argument |
| // (first argument is helpPattern) |
| printLine( |
| String.format( |
| "No help for '%s'; command is unknown or undocumented", |
| args.get(1).get(0))); |
| genericHelpRunnable.run(args); |
| } |
| }, |
| HELP_PATTERN, |
| null); |
| |
| // Add a fallback input handler |
| trie.put( |
| new ArgRunnable<CaptureList>() { |
| @Override |
| public void run(CaptureList args) { |
| if (args.isEmpty()) { |
| // User hit <Enter> with a blank line |
| return; |
| } |
| |
| // Command will be the only capture in the first argument |
| printLine(String.format("Unknown command: '%s'", args.get(0).get(0))); |
| genericHelpRunnable.run(args); |
| } |
| }, |
| (Pattern) null); |
| } |
| |
| /** |
| * Return the generic help string to display |
| * |
| * @param genericHelp a list of {@link String} representing the generic help to be aggregated. |
| */ |
| protected String getGenericHelpString(List<String> genericHelp) { |
| return ArrayUtil.join(LINE_SEPARATOR, genericHelp); |
| } |
| |
| /** |
| * A utility function to return the arguments that were passed to an {@link ArgRunnable}. In |
| * particular, it expects all first-level elements of {@code cl} after {@code argIdx} to be |
| * singleton {@link List}s. It will then coalesce the first element of each of those singleton |
| * {@link List}s as a single {@link List}. |
| * |
| * @param argIdx The zero-based index of the first argument. |
| * @param cl The {@link CaptureList} of arguments that was passed to the {@link ArgRunnable} |
| * @return A flattened {@link List} of arguments that were passed to the {@link ArgRunnable} |
| * @throws IllegalArgumentException if the data isn't formatted as expected |
| * @throws IndexOutOfBoundsException if {@code argIdx} isn't consistent with {@code cl} |
| */ |
| static List<String> getFlatArgs(int argIdx, CaptureList cl) { |
| if (argIdx < 0 || argIdx >= cl.size()) { |
| throw new IndexOutOfBoundsException( |
| String.format("argIdx is %d, cl size is %d", argIdx, cl.size())); |
| } |
| |
| List<String> flat = new ArrayList<String>(cl.size() - argIdx); |
| ListIterator<List<String>> iter = cl.listIterator(argIdx); |
| while (iter.hasNext()) { |
| List<String> single = iter.next(); |
| int len = single.size(); |
| if (len != 1) { |
| throw new IllegalArgumentException( |
| String.format( |
| "Expected a singleton List, but got a List with %d elements: %s", |
| len, single.toString())); |
| } |
| flat.add(single.get(0)); |
| } |
| |
| return flat; |
| } |
| |
| /** Utility function to actually parse and execute a command file. */ |
| void runCmdfile(String cmdfileName, List<String> extraArgs) { |
| try { |
| mScheduler.addCommandFile(cmdfileName, extraArgs); |
| } catch (ConfigurationException e) { |
| printLine(String.format("Failed to run %s: %s", cmdfileName, e)); |
| if (mScheduler.shouldShutdownOnCmdfileError()) { |
| printLine("shutdownOnCmdFileError is enabled, stopping TF"); |
| mScheduler.shutdown(); |
| } |
| reportProtoResults(e); |
| } |
| } |
| |
| private void reportProtoResults(Exception e) { |
| String protoRes = System.getenv(AutomatedReporters.PROTO_REPORTING_FILE); |
| if (protoRes == null) { |
| printLine( |
| String.format( |
| "No %s specified to output results", |
| AutomatedReporters.PROTO_REPORTING_FILE)); |
| return; |
| } |
| if (new File(protoRes).exists()) { |
| printLine(String.format("File %s already exists", protoRes)); |
| return; |
| } |
| FileProtoResultReporter reporter = new FileProtoResultReporter(); |
| reporter.setOutputFile(new File(protoRes)); |
| reporter.setDelimitedOutput(false); |
| reporter.invocationStarted(new InvocationContext()); |
| FailureDescription failure = |
| TestInvocation.createFailureFromException(e, FailureStatus.INFRA_FAILURE); |
| reporter.invocationFailed(failure); |
| reporter.invocationEnded(0L); |
| } |
| |
| /** |
| * Add commands to create the default Console experience |
| * |
| * <p>Adds relevant documentation to {@code genericHelp} and {@code commandHelp}. |
| * |
| * @param trie The {@link RegexTrie} to add the commands to |
| * @param genericHelp A {@link List} of lines to print when the user runs the "help" command |
| * with no arguments. |
| * @param commandHelp A {@link Map} containing documentation for any new commands that may have |
| * been added. The key is a regular expression to use as a key for {@link RegexTrie}. The |
| * value should be a String containing the help text to print for that command. |
| */ |
| void addDefaultCommands( |
| RegexTrie<Runnable> trie, List<String> genericHelp, Map<String, String> commandHelp) { |
| |
| // Help commands |
| genericHelp.add( |
| "Enter 'q' or 'exit' to exit. Use '--wait-for-command|-c' to exit only after all" |
| + " commands have executed."); |
| genericHelp.add("Enter 'kill' to attempt to forcibly exit, by shutting down adb"); |
| genericHelp.add(""); |
| genericHelp.add("Enter 'help all' to see all embedded documentation at once."); |
| genericHelp.add(""); |
| genericHelp.add("Enter 'help list' for help with 'list' commands"); |
| genericHelp.add("Enter 'help run' for help with 'run' commands"); |
| genericHelp.add("Enter 'help invocation' for help with 'invocation' commands"); |
| genericHelp.add("Enter 'help dump' for help with 'dump' commands"); |
| genericHelp.add("Enter 'help set' for help with 'set' commands"); |
| genericHelp.add("Enter 'help remove' for help with 'remove' commands"); |
| genericHelp.add("Enter 'help debug' for help with 'debug' commands"); |
| genericHelp.add("Enter 'version' to get the current version of Tradefed"); |
| |
| commandHelp.put( |
| LIST_PATTERN, |
| String.format( |
| "%s help:" |
| + LINE_SEPARATOR |
| + "\ti[nvocations] List all invocation threads" |
| + LINE_SEPARATOR |
| + "\td[evices] List all detected or known devices" |
| + LINE_SEPARATOR |
| + "\td[devices] all List all devices including placeholders" |
| + LINE_SEPARATOR |
| + "\tc[ommands] List all commands currently waiting to" |
| + " be executed" |
| + LINE_SEPARATOR |
| + "\tc[ommands] [pattern] List all commands matching the pattern" |
| + " and currently waiting to be executed" |
| + LINE_SEPARATOR |
| + "\tconfigs List all known configurations" |
| + LINE_SEPARATOR, |
| LIST_PATTERN)); |
| |
| commandHelp.put( |
| DUMP_PATTERN, |
| String.format( |
| "%s help:" |
| + LINE_SEPARATOR |
| + "\ts[tack] Dump the stack traces of all threads" |
| + LINE_SEPARATOR |
| + "\tl[ogs] Dump the logs of all invocations to files" |
| + LINE_SEPARATOR |
| + "\tb[ugreport] Dump a bugreport for the running Tradefed" |
| + " instance" |
| + LINE_SEPARATOR |
| + "\tc[onfig] <config> Dump the content of the specified config" |
| + LINE_SEPARATOR |
| + "\tcommandQueue Dump the contents of the commmand" |
| + " execution queue" |
| + LINE_SEPARATOR |
| + "\tcommands Dump all the config XML for the commands" |
| + " waiting to be executed" |
| + LINE_SEPARATOR |
| + "\tcommands [pattern] Dump all the config XML for the commands" |
| + " matching the pattern and waiting to be executed" |
| + LINE_SEPARATOR |
| + "\te[nv] Dump the environment variables available" |
| + " to test harness process" |
| + LINE_SEPARATOR |
| + "\tu[ptime] Dump how long the TradeFed process has" |
| + " been running" |
| + LINE_SEPARATOR, |
| DUMP_PATTERN)); |
| |
| commandHelp.put( |
| RUN_PATTERN, |
| String.format( |
| "%s help:" |
| + LINE_SEPARATOR |
| + "\tcommand <config> [options] Run the specified command" |
| + LINE_SEPARATOR |
| + "\t<config> [options] Shortcut for the above: run" |
| + " specified command" |
| + LINE_SEPARATOR |
| + "\tcmdfile <cmdfile.txt> Run the specified" |
| + " commandfile" |
| + LINE_SEPARATOR |
| + "\tcommandAndExit <config> [options] Run the specified command," |
| + " and run 'exit -c' immediately afterward" |
| + LINE_SEPARATOR |
| + "\tcmdfileAndExit <cmdfile.txt> Run the specified" |
| + " commandfile, and run 'exit -c' immediately afterward" |
| + LINE_SEPARATOR, |
| RUN_PATTERN)); |
| |
| commandHelp.put( |
| SET_PATTERN, |
| String.format( |
| "%s help:" |
| + LINE_SEPARATOR |
| + "\tlog-level-display <level> Sets the global display log level" |
| + " to <level>" |
| + LINE_SEPARATOR, |
| SET_PATTERN)); |
| |
| commandHelp.put( |
| REMOVE_PATTERN, |
| String.format( |
| "%s help:" |
| + LINE_SEPARATOR |
| + "\tremove allCommands Remove all commands currently waiting to" |
| + " be executed" |
| + LINE_SEPARATOR, |
| REMOVE_PATTERN)); |
| |
| commandHelp.put( |
| DEBUG_PATTERN, |
| String.format( |
| "%s help:" |
| + LINE_SEPARATOR |
| + "\tgc Attempt to force a GC" |
| + LINE_SEPARATOR, |
| DEBUG_PATTERN)); |
| |
| commandHelp.put( |
| INVOC_PATTERN, |
| String.format( |
| "%s help:" |
| + LINE_SEPARATOR |
| + "\ti[nvocation] [Command Id] Information of the" |
| + " invocation thread" |
| + LINE_SEPARATOR |
| + "\ti[nvocation] [Command Id] stop Notify to stop the invocation" |
| + LINE_SEPARATOR, |
| INVOC_PATTERN)); |
| |
| // Handle quit commands |
| trie.put(new QuitRunnable(), EXIT_PATTERN, null); |
| trie.put(new QuitRunnable(), EXIT_PATTERN); |
| trie.put(new ForceQuitRunnable(), "kill"); |
| |
| // List commands |
| trie.put( |
| new Runnable() { |
| @Override |
| public void run() { |
| mScheduler.displayInvocationsInfo(new PrintWriter(System.out, true)); |
| } |
| }, |
| LIST_PATTERN, |
| "i(?:nvocations)?"); |
| trie.put( |
| new Runnable() { |
| @Override |
| public void run() { |
| IDeviceManager manager = GlobalConfiguration.getDeviceManagerInstance(); |
| manager.displayDevicesInfo(new PrintWriter(System.out, true), false); |
| } |
| }, |
| LIST_PATTERN, |
| "d(?:evices)?"); |
| trie.put( |
| new Runnable() { |
| @Override |
| public void run() { |
| IDeviceManager manager = GlobalConfiguration.getDeviceManagerInstance(); |
| manager.displayDevicesInfo(new PrintWriter(System.out, true), true); |
| } |
| }, |
| LIST_PATTERN, |
| "d(?:evices)?", |
| "all"); |
| trie.put( |
| new Runnable() { |
| @Override |
| public void run() { |
| mScheduler.displayCommandsInfo(new PrintWriter(System.out, true), null); |
| } |
| }, |
| LIST_PATTERN, |
| LIST_COMMANDS_PATTERN); |
| ArgRunnable<CaptureList> listCmdRun = |
| new ArgRunnable<CaptureList>() { |
| @Override |
| public void run(CaptureList args) { |
| // Skip 2 tokens to get past listPattern and "commands" |
| String pattern = args.get(2).get(0); |
| mScheduler.displayCommandsInfo(new PrintWriter(System.out, true), pattern); |
| } |
| }; |
| trie.put(listCmdRun, LIST_PATTERN, LIST_COMMANDS_PATTERN, "(.*)"); |
| trie.put( |
| new Runnable() { |
| @Override |
| public void run() { |
| printLine( |
| "Use 'run command <configuration_name> --help' to get list of" |
| + " options for a configuration"); |
| printLine( |
| "Use 'dump config <configuration_name>' to display the" |
| + " configuration's XML content."); |
| printLine(""); |
| printLine("Available configurations include:"); |
| getConfigurationFactory().printHelp(System.out); |
| } |
| }, |
| LIST_PATTERN, |
| "configs"); |
| |
| // Invocation commands |
| trie.put( |
| new ArgRunnable<CaptureList>() { |
| @Override |
| public void run(CaptureList args) { |
| int invocId = Integer.parseInt(args.get(1).get(0)); |
| String info = mScheduler.getInvocationInfo(invocId); |
| if (info != null) { |
| printLine(String.format("invocation %s: %s", invocId, info)); |
| } else { |
| printLine( |
| String.format( |
| "No information found for invocation %s.", invocId)); |
| } |
| } |
| }, |
| INVOC_PATTERN, |
| "([0-9]*)"); |
| trie.put( |
| new ArgRunnable<CaptureList>() { |
| @Override |
| public void run(CaptureList args) { |
| int invocId = Integer.parseInt(args.get(1).get(0)); |
| if (mScheduler.stopInvocation(invocId)) { |
| printLine( |
| String.format( |
| "Invocation %s has been requested to stop." |
| + " It may take some times.", |
| invocId)); |
| } else { |
| printLine( |
| String.format( |
| "Could not stop invocation %s, try 'list invocation'" |
| + " or 'invocation %s' for more information.", |
| invocId, invocId)); |
| } |
| } |
| }, |
| INVOC_PATTERN, |
| "([0-9]*)", |
| "stop"); |
| |
| // Dump commands |
| trie.put( |
| new Runnable() { |
| @Override |
| public void run() { |
| dumpStacks(System.out); |
| } |
| }, |
| DUMP_PATTERN, |
| "s(?:tacks?)?"); |
| trie.put( |
| new Runnable() { |
| @Override |
| public void run() { |
| dumpLogs(); |
| } |
| }, |
| DUMP_PATTERN, |
| "l(?:ogs?)?"); |
| trie.put( |
| new Runnable() { |
| @Override |
| public void run() { |
| dumpTfBugreport(); |
| } |
| }, |
| DUMP_PATTERN, |
| "b(?:ugreport?)?"); |
| trie.put( |
| new Runnable() { |
| @Override |
| public void run() { |
| printElapsedTime(); |
| } |
| }, |
| DUMP_PATTERN, |
| "u(?:ptime?)?"); |
| ArgRunnable<CaptureList> dumpConfigRun = |
| new ArgRunnable<CaptureList>() { |
| @Override |
| public void run(CaptureList args) { |
| // Skip 2 tokens to get past dumpPattern and "config" |
| String configArg = args.get(2).get(0); |
| getConfigurationFactory().dumpConfig(configArg, System.out); |
| } |
| }; |
| trie.put(dumpConfigRun, DUMP_PATTERN, "c(?:onfig?)?", "(.*)"); |
| |
| trie.put( |
| new Runnable() { |
| @Override |
| public void run() { |
| mScheduler.displayCommandQueue(new PrintWriter(System.out, true)); |
| } |
| }, |
| DUMP_PATTERN, |
| "commandQueue"); |
| |
| trie.put( |
| new Runnable() { |
| @Override |
| public void run() { |
| mScheduler.dumpCommandsXml(new PrintWriter(System.out, true), null); |
| } |
| }, |
| DUMP_PATTERN, |
| LIST_COMMANDS_PATTERN); |
| ArgRunnable<CaptureList> dumpCmdRun = |
| new ArgRunnable<CaptureList>() { |
| @Override |
| public void run(CaptureList args) { |
| // Skip 2 tokens to get past listPattern and "commands" |
| String pattern = args.get(2).get(0); |
| mScheduler.dumpCommandsXml(new PrintWriter(System.out, true), pattern); |
| } |
| }; |
| trie.put(dumpCmdRun, DUMP_PATTERN, LIST_COMMANDS_PATTERN, "(.*)"); |
| |
| trie.put( |
| new Runnable() { |
| @Override |
| public void run() { |
| dumpEnv(); |
| } |
| }, |
| DUMP_PATTERN, |
| "e(?:nv)?"); |
| |
| // Run commands |
| ArgRunnable<CaptureList> runRunCommand = |
| new ArgRunnable<CaptureList>() { |
| @Override |
| public void run(CaptureList args) { |
| // The second argument "command" may also be missing, if the |
| // caller used the shortcut. |
| int startIdx = 1; |
| if (args.get(1).isEmpty()) { |
| // Empty array (that is, not even containing an empty string) means that |
| // we matched and skipped /(?:singleC|c)ommand/ |
| startIdx = 2; |
| } |
| |
| String[] flatArgs = new String[args.size() - startIdx]; |
| for (int i = startIdx; i < args.size(); i++) { |
| flatArgs[i - startIdx] = args.get(i).get(0); |
| } |
| try { |
| mScheduler.addCommand(flatArgs); |
| } catch (ConfigurationException e) { |
| printLine( |
| String.format( |
| "Failed to run command: %s\n%s", |
| e.toString(), StreamUtil.getStackTrace(e))); |
| } |
| } |
| }; |
| trie.put(runRunCommand, RUN_PATTERN, "c(?:ommand)?", null); |
| trie.put(runRunCommand, RUN_PATTERN, null); |
| trie.put( |
| new Runnable() { |
| @Override |
| public void run() { |
| String version = VersionParser.fetchVersion(); |
| if (version != null) { |
| printLine(version); |
| } else { |
| printLine("Failed to fetch version information for Tradefed."); |
| } |
| } |
| }, |
| VERSION_PATTERN); |
| |
| ArgRunnable<CaptureList> runAndExitCommand = |
| new ArgRunnable<CaptureList>() { |
| @Override |
| public void run(CaptureList args) { |
| // Skip 2 tokens to get past runPattern and "singleCommand" |
| String[] flatArgs = new String[args.size() - 2]; |
| for (int i = 2; i < args.size(); i++) { |
| flatArgs[i - 2] = args.get(i).get(0); |
| } |
| try { |
| if (mScheduler.addCommand(flatArgs).first) { |
| mScheduler.shutdownOnEmpty(); |
| } |
| } catch (ConfigurationException e) { |
| printLine("Failed to run command: " + e.toString()); |
| } |
| |
| // Intentionally kill the console before CommandScheduler finishes |
| mShouldExit.set(true); |
| } |
| }; |
| trie.put(runAndExitCommand, RUN_PATTERN, "s(?:ingleCommand)?", null); |
| trie.put(runAndExitCommand, RUN_PATTERN, "commandAndExit", null); |
| |
| // Missing required argument: show help |
| // FIXME: fix this functionality |
| // trie.put(runHelpRun, runPattern, "(?:singleC|c)ommand"); |
| |
| final ArgRunnable<CaptureList> runRunCmdfile = |
| new ArgRunnable<CaptureList>() { |
| @Override |
| public void run(CaptureList args) { |
| // Skip 2 tokens to get past runPattern and "cmdfile". We're guaranteed to |
| // have at |
| // least 3 tokens if we got #run. |
| int startIdx = 2; |
| List<String> flatArgs = getFlatArgs(startIdx, args); |
| String file = flatArgs.get(0); |
| List<String> extraArgs = flatArgs.subList(1, flatArgs.size()); |
| printLine( |
| String.format( |
| "Attempting to run cmdfile %s with args %s", |
| file, extraArgs.toString())); |
| runCmdfile(file, extraArgs); |
| } |
| }; |
| trie.put(runRunCmdfile, RUN_PATTERN, "cmdfile", "(.*)"); |
| trie.put(runRunCmdfile, RUN_PATTERN, "cmdfile", "(.*)", null); |
| |
| ArgRunnable<CaptureList> runRunCmdfileAndExit = |
| new ArgRunnable<CaptureList>() { |
| @Override |
| public void run(CaptureList args) { |
| runRunCmdfile.run(args); |
| mScheduler.shutdownOnEmpty(); |
| } |
| }; |
| trie.put(runRunCmdfileAndExit, RUN_PATTERN, "cmdfileAndExit", "(.*)"); |
| trie.put(runRunCmdfileAndExit, RUN_PATTERN, "cmdfileAndExit", "(.*)", null); |
| |
| ArgRunnable<CaptureList> runRunAllCmdfilesAndExit = |
| new ArgRunnable<CaptureList>() { |
| @Override |
| public void run(CaptureList args) { |
| // skip 2 tokens to get past runPattern and "allCmdfilesAndExit" |
| if (args.size() <= 2) { |
| printLine("No cmdfiles specified!"); |
| } else { |
| // Each group should have exactly one element, given how the null |
| // wildcard |
| // operates; so we flatten them. |
| for (String cmdfile : getFlatArgs(2 /* startIdx */, args)) { |
| runCmdfile(cmdfile, new ArrayList<String>(0)); |
| } |
| } |
| mScheduler.shutdownOnEmpty(); |
| } |
| }; |
| trie.put(runRunAllCmdfilesAndExit, RUN_PATTERN, "allCmdfilesAndExit"); |
| trie.put(runRunAllCmdfilesAndExit, RUN_PATTERN, "allCmdfilesAndExit", null); |
| |
| // Missing required argument: show help |
| // FIXME: fix this functionality |
| // trie.put(runHelpRun, runPattern, "cmdfile"); |
| |
| // Set commands |
| ArgRunnable<CaptureList> runSetLog = |
| new ArgRunnable<CaptureList>() { |
| @Override |
| public void run(CaptureList args) { |
| // Skip 2 tokens to get past "set" and "log-level-display" |
| String logLevelStr = args.get(2).get(0); |
| LogLevel newLogLevel = LogLevel.getByString(logLevelStr); |
| LogLevel currentLogLevel = |
| LogRegistry.getLogRegistry().getGlobalLogDisplayLevel(); |
| if (newLogLevel != null) { |
| LogRegistry.getLogRegistry().setGlobalLogDisplayLevel(newLogLevel); |
| // Make sure that the level was set. |
| currentLogLevel = |
| LogRegistry.getLogRegistry().getGlobalLogDisplayLevel(); |
| if (currentLogLevel != null) { |
| printLine( |
| String.format( |
| "Log level now set to '%s'.", currentLogLevel)); |
| } |
| } else { |
| if (currentLogLevel == null) { |
| printLine(String.format("Invalid log level '%s'.", newLogLevel)); |
| } else { |
| printLine( |
| String.format( |
| "Invalid log level '%s'; log level remains at" |
| + " '%s'.", |
| newLogLevel, currentLogLevel)); |
| } |
| } |
| } |
| }; |
| trie.put(runSetLog, SET_PATTERN, "log-level-display", "(.*)"); |
| |
| // Debug commands |
| trie.put( |
| new Runnable() { |
| @Override |
| public void run() { |
| System.gc(); |
| } |
| }, |
| DEBUG_PATTERN, |
| "gc"); |
| |
| // Remove commands |
| trie.put( |
| new Runnable() { |
| @Override |
| public void run() { |
| mScheduler.removeAllCommands(); |
| } |
| }, |
| REMOVE_PATTERN, |
| "allCommands"); |
| } |
| |
| /** Print the uptime of the Tradefed process. */ |
| private void printElapsedTime() { |
| long elapsedTime = System.currentTimeMillis() - mConsoleStartTime; |
| String elapsed = |
| String.format( |
| "TF has been running for %s", TimeUtil.formatElapsedTime(elapsedTime)); |
| printLine(elapsed); |
| } |
| |
| /** |
| * Get input from the console |
| * |
| * @return A {@link String} containing the input to parse and run. Will return {@code null} if |
| * console is not available or user entered EOF ({@code ^D}). |
| */ |
| @VisibleForTesting |
| String getConsoleInput() throws IOException { |
| if (mConsoleReader != null) { |
| try { |
| return mConsoleReader.readLine(getConsolePrompt()); |
| } catch (EndOfFileException e) { |
| return null; |
| } |
| } else { |
| return null; |
| } |
| } |
| |
| /** @return the text {@link String} to display for the console prompt */ |
| protected String getConsolePrompt() { |
| return CONSOLE_PROMPT; |
| } |
| |
| /** |
| * Display a line of text on console |
| * |
| * @param output |
| */ |
| protected void printLine(String output) { |
| System.out.print(output); |
| System.out.println(); |
| } |
| |
| /** |
| * Print the line to a Printwriter |
| * |
| * @param output |
| */ |
| protected void printLine(String output, PrintStream pw) { |
| pw.print(output); |
| pw.println(); |
| } |
| |
| /** |
| * Execute a command. |
| * |
| * <p>Exposed for unit testing |
| */ |
| @SuppressWarnings("unchecked") |
| void executeCmdRunnable(Runnable command, CaptureList groups) { |
| try { |
| if (command instanceof ArgRunnable) { |
| // FIXME: verify that command implements ArgRunnable<CaptureList> instead |
| // FIXME: of just ArgRunnable |
| ((ArgRunnable<CaptureList>) command).run(groups); |
| } else { |
| command.run(); |
| } |
| } catch (RuntimeException e) { |
| e.printStackTrace(); |
| } |
| } |
| |
| /** |
| * Return whether we should expect the console to be usable. |
| * |
| * <p>Exposed for unit testing. |
| */ |
| @SuppressWarnings("SystemConsoleNull") // https://errorprone.info/bugpattern/SystemConsoleNull |
| boolean isConsoleFunctional() { |
| java.io.Console systemConsole = System.console(); |
| if (Runtime.version().feature() < 22) { |
| return systemConsole != null; |
| } |
| try { |
| return (Boolean) java.io.Console.class.getMethod("isTerminal").invoke(systemConsole); |
| } catch (ReflectiveOperationException e) { |
| throw new LinkageError(e.getMessage(), e); |
| } |
| } |
| |
| /** |
| * The main method to launch the console. Will keep running until shutdown command is issued. |
| */ |
| @Override |
| public void run() { |
| List<String> arrrgs = mMainArgs; |
| |
| if (mScheduler == null) { |
| throw new IllegalStateException("command scheduler hasn't been set"); |
| } |
| |
| try { |
| // Check System.console() since jline doesn't seem to consistently know whether or not |
| // the console is functional. |
| if (!isConsoleFunctional()) { |
| if (arrrgs.isEmpty()) { |
| printLine("No commands for non-interactive mode; exiting."); |
| // FIXME: need to run the scheduler here so that the things blocking on it |
| // FIXME: will be released. |
| mScheduler.start(); |
| mScheduler.await(); |
| return; |
| } else { |
| printLine("Non-interactive mode: Running initial command then exiting."); |
| mShouldExit.set(true); |
| } |
| } |
| |
| // Wait for the CommandScheduler to start. It will hold the JVM open (since the Console |
| // thread is a Daemon thread), and also we require it to have started so that we can |
| // start processing user input. |
| mScheduler.start(); |
| mScheduler.await(); |
| |
| String input = ""; |
| CaptureList groups = new CaptureList(); |
| String[] tokens; |
| |
| // Note: since Console is a daemon thread, the JVM may exit without us actually leaving |
| // this read loop. This is by design. |
| do { |
| if (arrrgs.isEmpty()) { |
| input = getConsoleInput(); |
| |
| if (input == null) { |
| // Usually the result of getting EOF on the console |
| printLine(""); |
| printLine("Received EOF; quitting..."); |
| mShouldExit.set(true); |
| break; |
| } |
| |
| tokens = null; |
| try { |
| tokens = QuotationAwareTokenizer.tokenizeLine(input); |
| } catch (IllegalArgumentException e) { |
| printLine(String.format("Invalid input: %s.", input)); |
| continue; |
| } |
| |
| if (tokens == null || tokens.length == 0) { |
| continue; |
| } |
| } else { |
| printLine( |
| String.format( |
| "Using commandline arguments as starting command: %s", arrrgs)); |
| if (mConsoleReader != null) { |
| // Add the starting command as the first item in the console history |
| // FIXME: this will not properly escape commands that were properly escaped |
| // FIXME: on the commandline. That said, it will still be more convenient |
| // FIXME: than copying by hand. |
| final String cmd = ArrayUtil.join(" ", arrrgs); |
| mConsoleReader.getHistory().add(cmd); |
| } |
| tokens = arrrgs.toArray(new String[0]); |
| if (arrrgs.get(0).matches(HELP_PATTERN)) { |
| // if started from command line for help, return to shell |
| mShouldExit.set(true); |
| } |
| arrrgs = Collections.emptyList(); |
| } |
| |
| Runnable command = mCommandTrie.retrieve(groups, tokens); |
| if (command != null) { |
| executeCmdRunnable(command, groups); |
| } else { |
| printLine( |
| String.format( |
| "Unable to handle command '%s'. Enter 'help' for help.", |
| tokens[0])); |
| } |
| RunUtil.getDefault().sleep(100); |
| } while (!mShouldExit.get()); |
| } catch (Exception e) { |
| printLine("Console received an unexpected exception (shown below); shutting down TF."); |
| e.printStackTrace(); |
| } finally { |
| mScheduler.shutdown(); |
| GlobalConfiguration.getInstance().cleanup(); |
| // Make sure that we don't quit with messages still in the buffers |
| System.err.flush(); |
| System.out.flush(); |
| } |
| } |
| |
| /** set the flag to exit the console. */ |
| @VisibleForTesting |
| void exitConsole() { |
| mShouldExit.set(true); |
| } |
| |
| void awaitScheduler() throws InterruptedException { |
| mScheduler.await(); |
| } |
| |
| /** |
| * Method for getting a {@link IConfigurationFactory}. |
| * |
| * <p>Exposed for unit testing. |
| */ |
| IConfigurationFactory getConfigurationFactory() { |
| return ConfigurationFactory.getInstance(); |
| } |
| |
| private void dumpStacks(PrintStream ps) { |
| Map<Thread, StackTraceElement[]> threadMap = Thread.getAllStackTraces(); |
| for (Map.Entry<Thread, StackTraceElement[]> threadEntry : threadMap.entrySet()) { |
| dumpThreadStack(threadEntry.getKey(), threadEntry.getValue(), ps); |
| } |
| } |
| |
| private void dumpThreadStack(Thread thread, StackTraceElement[] trace, PrintStream ps) { |
| printLine(String.format("%s", thread), ps); |
| for (int i = 0; i < trace.length; i++) { |
| printLine(String.format("\t%s", trace[i]), ps); |
| } |
| printLine("", ps); |
| } |
| |
| private void dumpLogs() { |
| LogRegistry.getLogRegistry().dumpLogs(); |
| } |
| |
| /** Dumps the environment variables to console, sorted by variable names */ |
| private void dumpEnv() { |
| // use TreeMap to sort variables by name |
| Map<String, String> env = new TreeMap<>(System.getenv()); |
| for (Map.Entry<String, String> entry : env.entrySet()) { |
| printLine(String.format("\t%s=%s", entry.getKey(), entry.getValue())); |
| } |
| } |
| |
| /** Dump a Tradefed Bugreport containing the stack traces and logs. */ |
| private void dumpTfBugreport() { |
| File tmpBugreportDir = null; |
| PrintStream ps = null; |
| try { |
| // dump stacks |
| tmpBugreportDir = FileUtil.createNamedTempDir("bugreport_tf"); |
| File tmpStackFile = FileUtil.createTempFile("dump_stacks_", ".log", tmpBugreportDir); |
| ps = new PrintStream(tmpStackFile); |
| dumpStacks(ps); |
| ps.flush(); |
| // dump logs |
| ((LogRegistry) LogRegistry.getLogRegistry()).dumpLogsToDir(tmpBugreportDir); |
| // add them to a zip and log. |
| File zippedBugreport = ZipUtil.createZip(tmpBugreportDir, "tradefed_bugreport_"); |
| printLine( |
| String.format("Output bugreport zip in %s", zippedBugreport.getAbsolutePath())); |
| } catch (IOException io) { |
| printLine("Error when trying to dump bugreport"); |
| } finally { |
| ps.close(); |
| FileUtil.recursiveDelete(tmpBugreportDir); |
| } |
| } |
| |
| /** |
| * Sets the console starting arguments. |
| * |
| * @param mainArgs the arguments |
| */ |
| public void setArgs(List<String> mainArgs) { |
| mMainArgs = mainArgs; |
| } |
| |
| private static class TerminateGRPCServers extends Thread { |
| private final TradefedFeatureServer mFeatureServer; |
| private final TestInvocationManagementServer mInvocationServer; |
| private final DeviceManagementGrpcServer mDeviceServer; |
| |
| public TerminateGRPCServers( |
| TradefedFeatureServer featureServer, |
| TestInvocationManagementServer invocationServer, |
| DeviceManagementGrpcServer deviceServer) { |
| mFeatureServer = featureServer; |
| mInvocationServer = invocationServer; |
| mDeviceServer = deviceServer; |
| } |
| |
| @Override |
| public void run() { |
| if (mFeatureServer != null) { |
| try { |
| mFeatureServer.shutdown(); |
| } catch (InterruptedException e) { |
| CLog.e(e); |
| } |
| } |
| if (mInvocationServer != null) { |
| try { |
| mInvocationServer.shutdown(); |
| } catch (InterruptedException e) { |
| CLog.e(e); |
| } |
| } |
| if (mDeviceServer != null) { |
| try { |
| mDeviceServer.shutdown(); |
| } catch (InterruptedException e) { |
| CLog.e(e); |
| } |
| } |
| } |
| } |
| |
| public static void main(final String[] mainArgs) |
| throws InterruptedException, ConfigurationException { |
| Console console = new Console(); |
| try { |
| startConsole(console, mainArgs); |
| } catch (ConfigurationException e) { |
| e.printStackTrace(); |
| System.exit(ExitCode.CONFIG_EXCEPTION.getCodeValue()); |
| } catch (InterruptedException interrupt) { |
| interrupt.printStackTrace(); |
| System.exit(ExitCode.THROWABLE_EXCEPTION.getCodeValue()); |
| } |
| } |
| |
| /** |
| * Starts the given Tradefed console with given args |
| * |
| * @param console the {@link Console} to start |
| * @param args the command line arguments |
| */ |
| public static void startConsole(Console console, String[] args) |
| throws InterruptedException, ConfigurationException { |
| ClearcutClient client = |
| new ClearcutClient( |
| TestSuiteInfo.getInstance().didLoadFromProperties() |
| ? TestSuiteInfo.getInstance().getName() |
| : ""); |
| Runtime.getRuntime().addShutdownHook(new TerminateClearcutClient(client)); |
| client.notifyTradefedStartEvent(); |
| TradefedFeatureServer server = null; |
| try { |
| server = new TradefedFeatureServer(); |
| server.start(); |
| } catch (RuntimeException e) { |
| System.out.println(String.format("Error starting feature server: %s", e)); |
| // Abort the start if we fail to start the server, it is a critical component. |
| throw e; |
| } |
| TestInvocationManagementServer invocationManagementServer = null; |
| DeviceManagementGrpcServer deviceManagementServer = null; |
| try { |
| List<String> nonGlobalArgs = GlobalConfiguration.createGlobalConfiguration(args); |
| GlobalConfiguration.getInstance().setup(); |
| if (server != null) { |
| GlobalConfiguration.getInstance().setTradefedFeatureServer(server); |
| } |
| console.setArgs(nonGlobalArgs); |
| console.setCommandScheduler(GlobalConfiguration.getInstance().getCommandScheduler()); |
| console.setKeyStoreFactory(GlobalConfiguration.getInstance().getKeyStoreFactory()); |
| console.setDaemon(true); |
| |
| GlobalConfiguration.getInstance().getCommandScheduler().setClearcutClient(client); |
| // Initialize the locks for the TF session |
| GlobalConfiguration.getInstance().getHostOptions().initConcurrentLocks(); |
| |
| console.start(); |
| |
| // Wait for the CommandScheduler to get started before we exit the main thread. See |
| // full |
| // explanation near the top of #run() |
| console.awaitScheduler(); |
| console.registerShutdownSignals(); |
| |
| // Gate the server starting to a port being explicitly defined |
| Integer deviceManagementPort = DeviceManagementGrpcServer.getPort(); |
| if (deviceManagementPort != null) { |
| try { |
| deviceManagementServer = |
| new DeviceManagementGrpcServer( |
| deviceManagementPort, |
| GlobalConfiguration.getDeviceManagerInstance(), |
| GlobalConfiguration.getInstance().getCommandScheduler()); |
| GlobalConfiguration.getInstance() |
| .setDeviceManagementServer(deviceManagementServer); |
| deviceManagementServer.start(); |
| } catch (RuntimeException e) { |
| System.out.println( |
| String.format("Error starting device management server: %s", e)); |
| } |
| } |
| Integer port = TestInvocationManagementServer.getPort(); |
| if (port != null) { |
| try { |
| invocationManagementServer = |
| new TestInvocationManagementServer( |
| port, |
| GlobalConfiguration.getInstance().getCommandScheduler(), |
| deviceManagementServer); |
| GlobalConfiguration.getInstance() |
| .setInvocationServer(invocationManagementServer); |
| // Start the server last to ensure that command scheduler is started |
| invocationManagementServer.start(); |
| } catch (RuntimeException e) { |
| System.out.println(String.format("Error starting invocation server: %s", e)); |
| } |
| } |
| } finally { |
| Runtime.getRuntime() |
| .addShutdownHook( |
| new TerminateGRPCServers( |
| server, invocationManagementServer, deviceManagementServer)); |
| } |
| } |
| } |