1 /* 2 * Copyright (C) 2017 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 package com.android.tradefed.sandbox; 17 18 import com.android.annotations.VisibleForTesting; 19 import com.android.tradefed.command.CommandRunner.ExitCode; 20 import com.android.tradefed.command.ICommandScheduler; 21 import com.android.tradefed.command.ICommandScheduler.IScheduledInvocationListener; 22 import com.android.tradefed.config.ConfigurationException; 23 import com.android.tradefed.config.GlobalConfiguration; 24 import com.android.tradefed.device.FreeDeviceState; 25 import com.android.tradefed.device.ITestDevice; 26 import com.android.tradefed.device.NoDeviceException; 27 import com.android.tradefed.invoker.IInvocationContext; 28 import com.android.tradefed.invoker.InvocationContext; 29 import com.android.tradefed.invoker.proto.InvocationContext.Context; 30 import com.android.tradefed.util.FileUtil; 31 import com.android.tradefed.util.SerializationUtil; 32 33 import org.json.JSONException; 34 import org.json.JSONObject; 35 36 import java.io.File; 37 import java.io.FileInputStream; 38 import java.io.IOException; 39 import java.util.ArrayList; 40 import java.util.Arrays; 41 import java.util.List; 42 import java.util.Map; 43 44 /** Runner associated with a {@link TradefedSandbox} that will allow executing the sandbox. */ 45 public class TradefedSandboxRunner { 46 public static final String EXCEPTION_KEY = "serialized_exception"; 47 48 private ICommandScheduler mScheduler; 49 private ExitCode mErrorCode = ExitCode.NO_ERROR; 50 TradefedSandboxRunner()51 public TradefedSandboxRunner() {} 52 getErrorCode()53 public ExitCode getErrorCode() { 54 return mErrorCode; 55 } 56 57 /** Initialize the required global configuration. */ 58 @VisibleForTesting initGlobalConfig(String[] args)59 void initGlobalConfig(String[] args) throws ConfigurationException { 60 GlobalConfiguration.createGlobalConfiguration(args); 61 } 62 63 /** Get the {@link ICommandScheduler} instance from the global configuration. */ 64 @VisibleForTesting getCommandScheduler()65 ICommandScheduler getCommandScheduler() { 66 return GlobalConfiguration.getInstance().getCommandScheduler(); 67 } 68 69 /** Prints the exception stack to stderr. */ 70 @VisibleForTesting printStackTrace(Throwable e)71 void printStackTrace(Throwable e) { 72 e.printStackTrace(); 73 File serializedException = null; 74 try { 75 serializedException = SerializationUtil.serialize(e); 76 JSONObject json = new JSONObject(); 77 json.put(EXCEPTION_KEY, serializedException.getAbsolutePath()); 78 System.err.println(json.toString()); 79 } catch (IOException | JSONException io) { 80 FileUtil.deleteFile(serializedException); 81 } 82 } 83 84 /** 85 * The main method to run the command. 86 * 87 * @param args the config name to run and its options 88 */ run(String[] args)89 public void run(String[] args) { 90 List<String> argList = new ArrayList<>(Arrays.asList(args)); 91 IInvocationContext context = null; 92 93 if (argList.size() < 2) { 94 mErrorCode = ExitCode.THROWABLE_EXCEPTION; 95 printStackTrace( 96 new RuntimeException("TradefedContainerRunner expect at least 2 args.")); 97 return; 98 } 99 100 File contextFile = new File(argList.remove(0)); 101 try { 102 Context c = Context.parseDelimitedFrom(new FileInputStream(contextFile)); 103 context = InvocationContext.fromProto(c); 104 } catch (IOException e) { 105 // Fallback to compatible old way 106 // TODO: Delete when parent has been deployed. 107 try { 108 context = (IInvocationContext) SerializationUtil.deserialize(contextFile, false); 109 } catch (IOException e2) { 110 printStackTrace(e); 111 printStackTrace(e2); 112 mErrorCode = ExitCode.THROWABLE_EXCEPTION; 113 return; 114 } 115 } 116 117 try { 118 initGlobalConfig(new String[] {}); 119 mScheduler = getCommandScheduler(); 120 mScheduler.start(); 121 mScheduler.execCommand( 122 context, new StubScheduledInvocationListener(), argList.toArray(new String[0])); 123 } catch (NoDeviceException e) { 124 printStackTrace(e); 125 mErrorCode = ExitCode.NO_DEVICE_ALLOCATED; 126 } catch (ConfigurationException e) { 127 printStackTrace(e); 128 mErrorCode = ExitCode.CONFIG_EXCEPTION; 129 } finally { 130 mScheduler.shutdownOnEmpty(); 131 } 132 try { 133 mScheduler.join(); 134 // If no error code has been raised yet, we checked the invocation error code. 135 if (ExitCode.NO_ERROR.equals(mErrorCode)) { 136 mErrorCode = mScheduler.getLastInvocationExitCode(); 137 } 138 } catch (InterruptedException e) { 139 e.printStackTrace(); 140 mErrorCode = ExitCode.THROWABLE_EXCEPTION; 141 } 142 if (!ExitCode.NO_ERROR.equals(mErrorCode) 143 && mScheduler.getLastInvocationThrowable() != null) { 144 // Print error to the stderr so that it can be recovered. 145 printStackTrace(mScheduler.getLastInvocationThrowable()); 146 } 147 } 148 main(final String[] mainArgs)149 public static void main(final String[] mainArgs) { 150 TradefedSandboxRunner console = new TradefedSandboxRunner(); 151 console.run(mainArgs); 152 System.exit(console.getErrorCode().getCodeValue()); 153 } 154 155 /** A stub {@link IScheduledInvocationListener} that does nothing. */ 156 public static class StubScheduledInvocationListener implements IScheduledInvocationListener { 157 @Override invocationComplete( IInvocationContext metadata, Map<ITestDevice, FreeDeviceState> devicesStates)158 public void invocationComplete( 159 IInvocationContext metadata, Map<ITestDevice, FreeDeviceState> devicesStates) { 160 // do nothing 161 } 162 } 163 } 164