blob: 8ab27d60b97ac2ad6afd4d4120261d0704f614d8 [file] [log] [blame]
/*
* Copyright (C) 2020 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.targetprep;
import com.android.tradefed.build.IDeviceBuildInfo;
import com.android.tradefed.config.GlobalConfiguration;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.OptionClass;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.DeviceUnresponsiveException;
import com.android.tradefed.device.IDeviceManager;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.device.ITestDevice.RecoveryMode;
import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.CommandStatus;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.IRunUtil;
import com.android.tradefed.util.RunUtil;
import com.android.tradefed.util.ZipUtil2;
import com.google.common.annotations.VisibleForTesting;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
/**
* A target preparer that flash the device with android common kernel generic image. Please see
* https://source.android.com/devices/architecture/kernel/android-common for details.
*/
@OptionClass(alias = "gki-device-flash-preparer")
public class GkiDeviceFlashPreparer extends BaseTargetPreparer {
private static final String GKI_BOOT_IMG = "gki_boot.img";
private static final String MKBOOTIMG = "mkbootimg";
private static final String OTATOOLS_ZIP = "otatools.zip";
private static final String RAMDISK_RECOVERY_IMG = "ramdisk_recovery.img";
private static final String KERNEL_IMAGE = "kernel_image"; // The Image.gz file key
private static final String VENDOR_BOOT_IMG = "vendor_boot.img";
private static final String DTBO_IMG = "dtbo.img";
// Wait time for device state to stablize in millisecond
private static final int STATE_STABLIZATION_WAIT_TIME = 60000;
@Option(
name = "device-boot-time",
description = "max time to wait for device to boot. Set as 5 minutes by default",
isTimeVal = true)
private long mDeviceBootTime = 5 * 60 * 1000;
// The temp directory for files generated by target preparer
private File mPreparerTmpDir;
/** {@inheritDoc} */
@Override
public void setUp(TestInformation testInfo)
throws TargetSetupError, BuildError, DeviceNotAvailableException {
ITestDevice device = testInfo.getDevice();
IDeviceBuildInfo buildInfo = (IDeviceBuildInfo) testInfo.getBuildInfo();
try {
mPreparerTmpDir = FileUtil.createTempDir("gki_preparer_setup");
validateGkiBootImg(device, buildInfo);
flashGki(device, buildInfo);
} catch (IOException ioe) {
throw new TargetSetupError(ioe.getMessage(), ioe, device.getDeviceDescriptor());
} finally {
FileUtil.recursiveDelete(mPreparerTmpDir);
}
// Wait some time after flashing the image.
getRunUtil().sleep(STATE_STABLIZATION_WAIT_TIME);
device.rebootUntilOnline();
if (device.enableAdbRoot()) {
device.setDate(null);
}
try {
device.setRecoveryMode(RecoveryMode.AVAILABLE);
device.waitForDeviceAvailable(mDeviceBootTime);
} catch (DeviceUnresponsiveException e) {
// assume this is a build problem
throw new DeviceFailedToBootError(
String.format(
"Device %s did not become available after flashing GKI. Exception: %s",
device.getSerialNumber(), e),
device.getDeviceDescriptor());
}
device.postBootSetup();
CLog.i("Device update completed on %s", device.getDeviceDescriptor());
}
/**
* Get a reference to the {@link IDeviceManager}
*
* @return the {@link IDeviceManager} to use
*/
@VisibleForTesting
IDeviceManager getDeviceManager() {
return GlobalConfiguration.getDeviceManagerInstance();
}
/**
* Get the {@link IRunUtil} instance to use.
*
* @return the {@link IRunUtil} to use
*/
@VisibleForTesting
protected IRunUtil getRunUtil() {
return RunUtil.getDefault();
}
/**
* Flash GKI images.
*
* @param device the {@link ITestDevice}
* @param buildInfo the {@link IDeviceBuildInfo} the device build info
* @throws TargetSetupError, DeviceNotAvailableException, IOException
*/
private void flashGki(ITestDevice device, IDeviceBuildInfo buildInfo)
throws TargetSetupError, DeviceNotAvailableException {
IDeviceManager deviceManager = getDeviceManager();
device.waitForDeviceOnline();
device.rebootIntoBootloader();
long start = System.currentTimeMillis();
deviceManager.takeFlashingPermit();
CLog.v(
"Flashing permit obtained after %ds",
TimeUnit.MILLISECONDS.toSeconds((System.currentTimeMillis() - start)));
// Don't allow interruptions during flashing operations.
getRunUtil().allowInterrupt(false);
try {
if (buildInfo.getFile(VENDOR_BOOT_IMG) != null) {
executeFastbootCmd(
device,
"flash",
"vendor_boot",
buildInfo.getFile(VENDOR_BOOT_IMG).getAbsolutePath());
}
if (buildInfo.getFile(DTBO_IMG) != null) {
executeFastbootCmd(
device, "flash", "dtbo", buildInfo.getFile(DTBO_IMG).getAbsolutePath());
}
executeFastbootCmd(
device, "flash", "boot", buildInfo.getFile(GKI_BOOT_IMG).getAbsolutePath());
} finally {
deviceManager.returnFlashingPermit();
// Allow interruption at the end no matter what.
getRunUtil().allowInterrupt(true);
CLog.v(
"Flashing permit returned after %ds",
TimeUnit.MILLISECONDS.toSeconds((System.currentTimeMillis() - start)));
}
}
/**
* Validate GKI boot image is expected. Throw exception if there is no valid boot.img.
*
* @param device the {@link ITestDevice}
* @param buildInfo the {@link IDeviceBuildInfo} the device build info
* @throws TargetSetupError, IOException if there is no valid gki boot.img
*/
public void validateGkiBootImg(ITestDevice device, IDeviceBuildInfo buildInfo)
throws TargetSetupError {
if (buildInfo.getFile(GKI_BOOT_IMG) != null
&& buildInfo.getFile(GKI_BOOT_IMG).length() > 0) {
return;
}
if (buildInfo.getFile(KERNEL_IMAGE) == null) {
throw new TargetSetupError(
KERNEL_IMAGE + " is not provided. Can not generate GKI boot.img.",
device.getDeviceDescriptor());
}
if (buildInfo.getFile(RAMDISK_RECOVERY_IMG) == null) {
throw new TargetSetupError(
RAMDISK_RECOVERY_IMG + " is not provided. Can not generate GKI boot.img.",
device.getDeviceDescriptor());
}
try {
File mkbootimg = getRequestedFile(device, MKBOOTIMG, buildInfo.getFile(OTATOOLS_ZIP));
File gkiBootImg = FileUtil.createTempFile("boot", ".img", mPreparerTmpDir);
String cmd =
String.format(
"%s --kernel %s --header_version 3 --base 0x00000000 "
+ "--pagesize 4096 --ramdisk %s -o %s",
mkbootimg.getAbsolutePath(),
buildInfo.getFile(KERNEL_IMAGE),
buildInfo.getFile(RAMDISK_RECOVERY_IMG),
gkiBootImg.getAbsolutePath());
executeHostCommand(device, cmd);
CLog.i("The GKI boot.img is of size %d", gkiBootImg.length());
if (gkiBootImg.length() > 0) {
buildInfo.setFile(GKI_BOOT_IMG, gkiBootImg, "0");
} else {
throw new TargetSetupError(
"The mkbootimg tool didn't generate a valid boot.img.",
device.getDeviceDescriptor());
}
} catch (IOException e) {
throw new TargetSetupError("Fail to get mkbootimg.", e, device.getDeviceDescriptor());
}
}
/**
* Flash device images.
*
* @param device the {@link ITestDevice}
* @param buildInfo the {@link IDeviceBuildInfo} the device build info
* @throws TargetSetupError, DeviceNotAvailableException
*/
private void flashDeviceImage(ITestDevice device, IDeviceBuildInfo buildInfo)
throws TargetSetupError, DeviceNotAvailableException {
IDeviceManager deviceManager = getDeviceManager();
long start = System.currentTimeMillis();
deviceManager.takeFlashingPermit();
CLog.v(
"Flashing permit obtained after %ds",
TimeUnit.MILLISECONDS.toSeconds((System.currentTimeMillis() - start)));
// don't allow interruptions during flashing operations.
getRunUtil().allowInterrupt(false);
try {
executeFastbootCmd(
device,
"--skip-reboot",
"--disable-verity",
"update",
buildInfo.getDeviceImageFile().getAbsolutePath());
} finally {
// Allow interruption at the end no matter what.
getRunUtil().allowInterrupt(true);
deviceManager.returnFlashingPermit();
CLog.v(
"Flashing permit returned after %ds",
TimeUnit.MILLISECONDS.toSeconds((System.currentTimeMillis() - start)));
}
}
/**
* Helper method to execute host command.
*
* @param device the {@link ITestDevice}
* @param command the command string
* @throws TargetSetupError, DeviceNotAvailableException
*/
private void executeHostCommand(ITestDevice device, final String command)
throws TargetSetupError {
final CommandResult result = getRunUtil().runTimedCmd(300000L, command.split("\\s+"));
switch (result.getStatus()) {
case SUCCESS:
CLog.i(
"Command %s finished successfully, stdout = [%s].",
command, result.getStdout());
break;
case FAILED:
throw new TargetSetupError(
String.format(
"Command %s failed, stdout = [%s], stderr = [%s].",
command, result.getStdout(), result.getStderr()),
device.getDeviceDescriptor());
case TIMED_OUT:
throw new TargetSetupError(
String.format("Command %s timed out.", command),
device.getDeviceDescriptor());
case EXCEPTION:
throw new TargetSetupError(
String.format("Exception occurred when running command %s.", command),
device.getDeviceDescriptor());
}
}
/**
* Get the requested file from the source file (zip or folder) by requested file name.
*
* <p>The provided source file can be a zip file. The method will unzip it to tempary directory
* and find the requested file by the provided file name.
*
* <p>The provided source file can be a file folder. The method will find the requestd file by
* the provided file name.
*
* @param device the {@link ITestDevice}
* @param requestedFileName the requeste file name String
* @param sourceFile the source file
* @return the file that is specified by the requested file name
* @throws TargetSetupError, IOException
*/
private File getRequestedFile(ITestDevice device, String requestedFileName, File sourceFile)
throws TargetSetupError, IOException {
File requestedFile = null;
if (sourceFile.getName().endsWith(".zip")) {
File destDir =
FileUtil.createTempDir(
FileUtil.getBaseName(sourceFile.getName()), mPreparerTmpDir);
ZipUtil2.extractZip(sourceFile, destDir);
requestedFile = FileUtil.findFile(destDir, requestedFileName);
} else if (sourceFile.isDirectory()) {
requestedFile = FileUtil.findFile(sourceFile, requestedFileName);
}
if (requestedFile == null || !requestedFile.exists()) {
throw new TargetSetupError(
String.format(
"Requested file with file_name %s does not exist in provided %s.",
requestedFileName, sourceFile),
device.getDeviceDescriptor());
}
return requestedFile;
}
/**
* Helper method to execute a fastboot command.
*
* @param device the {@link ITestDevice} to execute command on
* @param cmdArgs the arguments to provide to fastboot
* @return String the stderr output from command if non-empty. Otherwise returns the stdout Some
* fastboot commands are weird in that they dump output to stderr on success case
* @throws DeviceNotAvailableException if device is not available
* @throws TargetSetupError if fastboot command fails
*/
private String executeFastbootCmd(ITestDevice device, String... cmdArgs)
throws DeviceNotAvailableException, TargetSetupError {
CLog.i("Execute fastboot command %s on %s", cmdArgs, device.getSerialNumber());
CommandResult result = device.executeLongFastbootCommand(cmdArgs);
CLog.v("fastboot stdout: " + result.getStdout());
CLog.v("fastboot stderr: " + result.getStderr());
CommandStatus cmdStatus = result.getStatus();
// fastboot command line output is in stderr even for successful run
if (result.getStderr().contains("FAILED")) {
// if output contains "FAILED", just override to failure
cmdStatus = CommandStatus.FAILED;
}
if (cmdStatus != CommandStatus.SUCCESS) {
throw new TargetSetupError(
String.format(
"fastboot command %s failed in device %s. stdout: %s, stderr: %s",
cmdArgs[0],
device.getSerialNumber(),
result.getStdout(),
result.getStderr()),
device.getDeviceDescriptor());
}
if (result.getStderr().length() > 0) {
return result.getStderr();
} else {
return result.getStdout();
}
}
}