blob: f2e631d24da7569598e008126415c9a5d5f66019 [file] [log] [blame]
/*
* Copyright (C) 2017 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.sandbox;
import com.android.annotations.VisibleForTesting;
import com.android.tradefed.command.CommandRunner.ExitCode;
import com.android.tradefed.command.ICommandScheduler;
import com.android.tradefed.command.ICommandScheduler.IScheduledInvocationListener;
import com.android.tradefed.config.ConfigurationException;
import com.android.tradefed.config.GlobalConfiguration;
import com.android.tradefed.device.FreeDeviceState;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.device.NoDeviceException;
import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.invoker.InvocationContext;
import com.android.tradefed.invoker.proto.InvocationContext.Context;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.SerializationUtil;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/** Runner associated with a {@link TradefedSandbox} that will allow executing the sandbox. */
public class TradefedSandboxRunner {
public static final String EXCEPTION_KEY = "serialized_exception";
private ICommandScheduler mScheduler;
private ExitCode mErrorCode = ExitCode.NO_ERROR;
public TradefedSandboxRunner() {}
public ExitCode getErrorCode() {
return mErrorCode;
}
/** Initialize the required global configuration. */
@VisibleForTesting
void initGlobalConfig(String[] args) throws ConfigurationException {
GlobalConfiguration.createGlobalConfiguration(args);
}
/** Get the {@link ICommandScheduler} instance from the global configuration. */
@VisibleForTesting
ICommandScheduler getCommandScheduler() {
return GlobalConfiguration.getInstance().getCommandScheduler();
}
/** Prints the exception stack to stderr. */
@VisibleForTesting
void printStackTrace(Throwable e) {
e.printStackTrace();
File serializedException = null;
try {
serializedException = SerializationUtil.serialize(e);
JSONObject json = new JSONObject();
json.put(EXCEPTION_KEY, serializedException.getAbsolutePath());
System.err.println(json.toString());
} catch (IOException | JSONException io) {
FileUtil.deleteFile(serializedException);
}
}
/**
* The main method to run the command.
*
* @param args the config name to run and its options
*/
public void run(String[] args) {
List<String> argList = new ArrayList<>(Arrays.asList(args));
IInvocationContext context = null;
if (argList.size() < 2) {
mErrorCode = ExitCode.THROWABLE_EXCEPTION;
printStackTrace(
new RuntimeException("TradefedContainerRunner expect at least 2 args."));
return;
}
File contextFile = new File(argList.remove(0));
try {
Context c = Context.parseDelimitedFrom(new FileInputStream(contextFile));
context = InvocationContext.fromProto(c);
} catch (IOException e) {
// Fallback to compatible old way
// TODO: Delete when parent has been deployed.
try {
context = (IInvocationContext) SerializationUtil.deserialize(contextFile, false);
} catch (IOException e2) {
printStackTrace(e);
printStackTrace(e2);
mErrorCode = ExitCode.THROWABLE_EXCEPTION;
return;
}
}
try {
initGlobalConfig(new String[] {});
mScheduler = getCommandScheduler();
mScheduler.start();
mScheduler.execCommand(
context, new StubScheduledInvocationListener(), argList.toArray(new String[0]));
} catch (NoDeviceException e) {
printStackTrace(e);
mErrorCode = ExitCode.NO_DEVICE_ALLOCATED;
} catch (ConfigurationException e) {
printStackTrace(e);
mErrorCode = ExitCode.CONFIG_EXCEPTION;
} finally {
mScheduler.shutdownOnEmpty();
}
try {
mScheduler.join();
// If no error code has been raised yet, we checked the invocation error code.
if (ExitCode.NO_ERROR.equals(mErrorCode)) {
mErrorCode = mScheduler.getLastInvocationExitCode();
}
} catch (InterruptedException e) {
e.printStackTrace();
mErrorCode = ExitCode.THROWABLE_EXCEPTION;
}
if (!ExitCode.NO_ERROR.equals(mErrorCode)
&& mScheduler.getLastInvocationThrowable() != null) {
// Print error to the stderr so that it can be recovered.
printStackTrace(mScheduler.getLastInvocationThrowable());
}
}
public static void main(final String[] mainArgs) {
TradefedSandboxRunner console = new TradefedSandboxRunner();
console.run(mainArgs);
System.exit(console.getErrorCode().getCodeValue());
}
/** A stub {@link IScheduledInvocationListener} that does nothing. */
public static class StubScheduledInvocationListener implements IScheduledInvocationListener {
@Override
public void invocationComplete(
IInvocationContext metadata, Map<ITestDevice, FreeDeviceState> devicesStates) {
// do nothing
}
}
}