1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.tradefed.command; 18 19 import com.android.ddmlib.Log.LogLevel; 20 import com.android.tradefed.clearcut.ClearcutClient; 21 import com.android.tradefed.clearcut.TerminateClearcutClient; 22 import com.android.tradefed.config.ConfigurationException; 23 import com.android.tradefed.config.GlobalConfiguration; 24 import com.android.tradefed.device.NoDeviceException; 25 import com.android.tradefed.invoker.tracing.ActiveTrace; 26 import com.android.tradefed.invoker.tracing.CloseableTraceScope; 27 import com.android.tradefed.invoker.tracing.TracingLogger; 28 import com.android.tradefed.log.LogUtil.CLog; 29 import com.android.tradefed.result.error.InfraErrorIdentifier; 30 import com.android.tradefed.service.TradefedFeatureServer; 31 import com.android.tradefed.testtype.suite.TestSuiteInfo; 32 import com.android.tradefed.util.FileUtil; 33 import com.android.tradefed.util.SerializationUtil; 34 import com.android.tradefed.util.SystemUtil; 35 36 import com.google.common.annotations.VisibleForTesting; 37 38 import org.json.JSONException; 39 import org.json.JSONObject; 40 41 import java.io.File; 42 import java.io.IOException; 43 import java.util.concurrent.CompletableFuture; 44 import java.util.concurrent.ExecutionException; 45 46 import sun.misc.Signal; 47 import sun.misc.SignalHandler; 48 49 /** 50 * An alternate TradeFederation entry point that will run command specified in command 51 * line arguments and then quit. 52 * <p/> 53 * Intended for use with a debugger and other non-interactive modes of operation. 54 * <p/> 55 * Expected arguments: [commands options] (config to run) 56 */ 57 public class CommandRunner { 58 private ICommandScheduler mScheduler; 59 private ExitCode mErrorCode = ExitCode.NO_ERROR; 60 61 public static final String EXCEPTION_KEY = "serialized_exception"; 62 public static final String START_FEATURE_SERVER = "START_FEATURE_SERVER"; 63 private static final long CHECK_DEVICE_TIMEOUT = 60000; 64 CommandRunner()65 public CommandRunner() {} 66 getErrorCode()67 public ExitCode getErrorCode() { 68 return mErrorCode; 69 } 70 71 /** 72 * Initialize the required global configuration. 73 */ 74 @VisibleForTesting initGlobalConfig(String[] args)75 void initGlobalConfig(String[] args) throws ConfigurationException { 76 GlobalConfiguration.createGlobalConfiguration(args); 77 GlobalConfiguration.getInstance().setup(); 78 } 79 80 /** Get the {@link ICommandScheduler} instance from the global configuration. */ 81 @VisibleForTesting getCommandScheduler()82 ICommandScheduler getCommandScheduler() { 83 return GlobalConfiguration.getInstance().getCommandScheduler(); 84 } 85 86 /** Prints the exception stack to stderr. */ 87 @VisibleForTesting printStackTrace(Throwable e)88 void printStackTrace(Throwable e) { 89 e.printStackTrace(); 90 File serializedException = null; 91 try { 92 serializedException = SerializationUtil.serialize(e); 93 JSONObject json = new JSONObject(); 94 json.put(EXCEPTION_KEY, serializedException.getAbsolutePath()); 95 System.err.println(json.toString()); 96 System.err.flush(); 97 } catch (IOException | JSONException io) { 98 io.printStackTrace(); 99 FileUtil.deleteFile(serializedException); 100 } 101 } 102 103 /** Returns the timeout after which to check for the command. */ 104 @VisibleForTesting getCheckDeviceTimeout()105 long getCheckDeviceTimeout() { 106 return CHECK_DEVICE_TIMEOUT; 107 } 108 109 /** 110 * The main method to run the command. 111 * 112 * @param args the config name to run and its options 113 */ run(String[] args)114 public void run(String[] args) { 115 try { 116 CompletableFuture<ClearcutClient> futureClient = 117 CompletableFuture.supplyAsync(() -> createClient()); 118 try (CloseableTraceScope ignored = new CloseableTraceScope("initGlobalConfig")) { 119 initGlobalConfig(args); 120 } 121 122 ClearcutClient client = futureClient.get(); 123 Runtime.getRuntime().addShutdownHook(new TerminateClearcutClient(client)); 124 client.notifyTradefedStartEvent(); 125 if (System.getenv(START_FEATURE_SERVER) != null) { 126 // Starting the server takes 100ms so do it in parallel 127 CompletableFuture.supplyAsync(() -> startFeatureSever()); 128 } 129 130 mScheduler = getCommandScheduler(); 131 mScheduler.setClearcutClient(client); 132 mScheduler.start(); 133 SignalHandler handler = 134 new SignalHandler() { 135 @Override 136 public void handle(Signal sig) { 137 CLog.logAndDisplay( 138 LogLevel.INFO, 139 String.format( 140 "Received signal %s. Shutting down.", sig.getName())); 141 mScheduler.shutdownHard(false); 142 } 143 }; 144 Signal.handle(new Signal("TERM"), handler); 145 146 mScheduler.addCommand(args); 147 } catch (ConfigurationException 148 | RuntimeException 149 | InterruptedException 150 | ExecutionException e) { 151 printStackTrace(e); 152 mErrorCode = ExitCode.CONFIG_EXCEPTION; 153 return; 154 } finally { 155 if (mScheduler != null) { 156 mScheduler.shutdownOnEmpty(); 157 } 158 } 159 try { 160 mScheduler.join(getCheckDeviceTimeout()); 161 // FIXME: if possible make the no_device allocated check deterministic. 162 // After 1 min we check if the command was executed. 163 if (mScheduler.getReadyCommandCount() > 0 164 && mScheduler.getExecutingCommandCount() == 0) { 165 printStackTrace( 166 new NoDeviceException( 167 "No device was allocated for the command.", 168 InfraErrorIdentifier.RUNNER_ALLOCATION_ERROR)); 169 mErrorCode = ExitCode.NO_DEVICE_ALLOCATED; 170 mScheduler.removeAllCommands(); 171 mScheduler.shutdown(); 172 return; 173 } 174 mScheduler.join(); 175 // If no error code has been raised yet, we checked the invocation error code. 176 if (ExitCode.NO_ERROR.equals(mErrorCode)) { 177 mErrorCode = mScheduler.getLastInvocationExitCode(); 178 } 179 } catch (InterruptedException e) { 180 e.printStackTrace(); 181 mErrorCode = ExitCode.THROWABLE_EXCEPTION; 182 } finally { 183 GlobalConfiguration.getInstance().cleanup(); 184 } 185 if (!ExitCode.NO_ERROR.equals(mErrorCode) 186 && mScheduler.getLastInvocationThrowable() != null) { 187 // Print error to the stderr so that it can be recovered. 188 printStackTrace(mScheduler.getLastInvocationThrowable()); 189 } 190 } 191 createClient()192 protected ClearcutClient createClient() { 193 try (CloseableTraceScope ignored = new CloseableTraceScope("createClient")) { 194 return new ClearcutClient( 195 TestSuiteInfo.getInstance().didLoadFromProperties() 196 ? TestSuiteInfo.getInstance().getName() 197 : ""); 198 } 199 } 200 main(final String[] mainArgs)201 public static void main(final String[] mainArgs) { 202 long pid = ProcessHandle.current().pid(); 203 long tid = Thread.currentThread().getId(); 204 ActiveTrace trace = null; 205 // Enable Full Tradefed tracing in local mode only for now 206 if (SystemUtil.isLocalMode()) { 207 trace = TracingLogger.createActiveTrace(pid, tid, true); 208 trace.startTracing(false); 209 } 210 CommandRunner console = new CommandRunner(); 211 try (CloseableTraceScope ignored = new CloseableTraceScope("end_to_end_command")) { 212 console.run(mainArgs); 213 } finally { 214 if (trace != null) { 215 trace.finalizeTracing(); 216 } 217 } 218 System.exit(console.getErrorCode().getCodeValue()); 219 } 220 221 /** 222 * Error codes that are possible to exit with. 223 */ 224 public static enum ExitCode { 225 NO_ERROR(0), 226 CONFIG_EXCEPTION(1), 227 NO_BUILD(2), 228 DEVICE_UNRESPONSIVE(3), 229 DEVICE_UNAVAILABLE(4), 230 FATAL_HOST_ERROR(5), 231 THROWABLE_EXCEPTION(6), 232 NO_DEVICE_ALLOCATED(7), 233 WRONG_JAVA_VERSION(8); 234 235 private final int mCodeValue; 236 ExitCode(int codeValue)237 ExitCode(int codeValue) { 238 mCodeValue = codeValue; 239 } 240 getCodeValue()241 public int getCodeValue() { 242 return mCodeValue; 243 } 244 } 245 startFeatureSever()246 private boolean startFeatureSever() { 247 try (CloseableTraceScope f = new CloseableTraceScope("start_fr_client")) { 248 TradefedFeatureServer server = new TradefedFeatureServer(); 249 server.start(); 250 GlobalConfiguration.getInstance().setTradefedFeatureServer(server); 251 } catch (RuntimeException e) { 252 System.out.println(String.format("Error starting feature server: %s", e)); 253 } 254 return true; 255 } 256 } 257