blob: 4ef04346c2eec5322b47884c7367d7e73b9e16d0 [file] [log] [blame]
/*
* Copyright (C) 2018 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.device.cloud;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.command.remote.DeviceDescriptor;
import com.android.tradefed.device.TestDeviceOptions;
import com.android.tradefed.device.cloud.AcloudConfigParser.AcloudKeys;
import com.android.tradefed.device.cloud.GceAvdInfo.GceStatus;
import com.android.tradefed.log.ITestLogger;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.ByteArrayInputStreamSource;
import com.android.tradefed.result.FileInputStreamSource;
import com.android.tradefed.result.InputStreamSource;
import com.android.tradefed.result.LogDataType;
import com.android.tradefed.targetprep.TargetSetupError;
import com.android.tradefed.util.ArrayUtil;
import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.CommandStatus;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.GoogleApiClientUtil;
import com.android.tradefed.util.IRunUtil;
import com.android.tradefed.util.RunUtil;
import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.compute.Compute;
import com.google.api.services.compute.Compute.Instances.GetSerialPortOutput;
import com.google.api.services.compute.ComputeScopes;
import com.google.api.services.compute.model.SerialPortOutput;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.net.HostAndPort;
import java.io.File;
import java.io.IOException;
import java.lang.ProcessBuilder.Redirect;
import java.security.GeneralSecurityException;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** Helper that manages the GCE calls to start/stop and collect logs from GCE. */
public class GceManager {
public static final String GCE_INSTANCE_NAME_KEY = "gce-instance-name";
public static final String GCE_INSTANCE_CLEANED_KEY = "gce-instance-clean-called";
private static final long BUGREPORT_TIMEOUT = 15 * 60 * 1000L;
private static final long REMOTE_FILE_OP_TIMEOUT = 10 * 60 * 1000L;
private static final Pattern BUGREPORTZ_RESPONSE_PATTERN = Pattern.compile("(OK:)(.*)");
private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
private static final List<String> SCOPES = Arrays.asList(ComputeScopes.COMPUTE_READONLY);
private DeviceDescriptor mDeviceDescriptor;
private TestDeviceOptions mDeviceOptions;
private IBuildInfo mBuildInfo;
private String mGceInstanceName = null;
private String mGceHost = null;
private GceAvdInfo mGceAvdInfo = null;
/**
* Ctor
*
* @param deviceDesc The {@link DeviceDescriptor} that will be associated with the GCE device.
* @param deviceOptions A {@link TestDeviceOptions} associated with the device.
* @param buildInfo A {@link IBuildInfo} describing the gce build to start.
*/
public GceManager(
DeviceDescriptor deviceDesc, TestDeviceOptions deviceOptions, IBuildInfo buildInfo) {
mDeviceDescriptor = deviceDesc;
mDeviceOptions = deviceOptions;
mBuildInfo = buildInfo;
if (!deviceOptions.allowGceCmdTimeoutOverride()) {
return;
}
int index = deviceOptions.getGceDriverParams().lastIndexOf("--boot-timeout");
if (index != -1 && deviceOptions.getGceDriverParams().size() > index + 1) {
String driverTimeoutStringSec = deviceOptions.getGceDriverParams().get(index + 1);
try {
// Add some extra time on top of Acloud: acloud boot the device then we expect
// the Tradefed online check to take a bit of time, use 3min as a safe overhead
long driverTimeoutMs =
Long.parseLong(driverTimeoutStringSec) * 1000 + 3 * 60 * 1000;
long gceCmdTimeoutMs = deviceOptions.getGceCmdTimeout();
deviceOptions.setGceCmdTimeout(driverTimeoutMs);
CLog.i(
"Replacing --gce-boot-timeout %s by --boot-timeout %s.",
gceCmdTimeoutMs, driverTimeoutMs);
} catch (NumberFormatException e) {
CLog.e(e);
}
}
}
/** @deprecated Use other constructors, we keep this temporarily for backward compatibility. */
@Deprecated
public GceManager(
DeviceDescriptor deviceDesc,
TestDeviceOptions deviceOptions,
IBuildInfo buildInfo,
List<IBuildInfo> testResourceBuildInfos) {
this(deviceDesc, deviceOptions, buildInfo);
}
/**
* Ctor, variation that can be used to provide the GCE instance name to use directly.
*
* @param deviceDesc The {@link DeviceDescriptor} that will be associated with the GCE device.
* @param deviceOptions A {@link TestDeviceOptions} associated with the device
* @param buildInfo A {@link IBuildInfo} describing the gce build to start.
* @param gceInstanceName The instance name to use.
* @param gceHost The host name or ip of the instance to use.
*/
public GceManager(
DeviceDescriptor deviceDesc,
TestDeviceOptions deviceOptions,
IBuildInfo buildInfo,
String gceInstanceName,
String gceHost) {
this(deviceDesc, deviceOptions, buildInfo);
mGceInstanceName = gceInstanceName;
mGceHost = gceHost;
}
public GceAvdInfo startGce() throws TargetSetupError {
return startGce(null);
}
/**
* Attempt to start a gce instance
*
* @return a {@link GceAvdInfo} describing the GCE instance. Could be a BOOT_FAIL instance.
* @throws TargetSetupError
*/
public GceAvdInfo startGce(String ipDevice) throws TargetSetupError {
mGceAvdInfo = null;
// For debugging purposes bypass.
if (mGceHost != null && mGceInstanceName != null) {
mGceAvdInfo =
new GceAvdInfo(
mGceInstanceName,
HostAndPort.fromString(mGceHost)
.withDefaultPort(mDeviceOptions.getRemoteAdbPort()));
return mGceAvdInfo;
}
// Add extra args.
File reportFile = null;
try {
reportFile = FileUtil.createTempFile("gce_avd_driver", ".json");
List<String> gceArgs = buildGceCmd(reportFile, mBuildInfo, ipDevice);
long driverTimeoutMs = getTestDeviceOptions().getGceCmdTimeout();
if (!getTestDeviceOptions().allowGceCmdTimeoutOverride()) {
long driverTimeoutSec =
Duration.ofMillis(driverTimeoutMs - 3 * 60 * 1000).toSeconds();
// --boot-timeout takes a value in seconds
gceArgs.add("--boot-timeout");
gceArgs.add(Long.toString(driverTimeoutSec));
driverTimeoutMs = driverTimeoutSec * 1000;
}
CLog.i("Launching GCE with %s", gceArgs.toString());
CommandResult cmd =
getRunUtil()
.runTimedCmd(
getTestDeviceOptions().getGceCmdTimeout(),
gceArgs.toArray(new String[gceArgs.size()]));
CLog.i("GCE driver stderr: %s", cmd.getStderr());
String instanceName = extractInstanceName(cmd.getStderr());
if (instanceName != null) {
mBuildInfo.addBuildAttribute(GCE_INSTANCE_NAME_KEY, instanceName);
} else {
CLog.w("Could not extract an instance name for the gce device.");
}
if (CommandStatus.TIMED_OUT.equals(cmd.getStatus())) {
String errors =
String.format(
"acloud errors: timeout after %dms, acloud did not return",
driverTimeoutMs);
if (instanceName != null) {
// If we managed to parse the instance name, report the boot failure so it
// can be shutdown.
mGceAvdInfo = new GceAvdInfo(instanceName, null, errors, GceStatus.BOOT_FAIL);
return mGceAvdInfo;
}
throw new TargetSetupError(errors, mDeviceDescriptor);
} else if (!CommandStatus.SUCCESS.equals(cmd.getStatus())) {
CLog.w("Error when booting the Gce instance, reading output of gce driver");
mGceAvdInfo =
GceAvdInfo.parseGceInfoFromFile(
reportFile, mDeviceDescriptor, mDeviceOptions.getRemoteAdbPort());
String errors = "";
if (mGceAvdInfo != null) {
// We always return the GceAvdInfo describing the instance when possible
// The caller can decide actions to be taken.
return mGceAvdInfo;
} else {
errors =
"Could not get a valid instance name, check the gce driver's output."
+ "The instance may not have booted up at all.";
CLog.e(errors);
throw new TargetSetupError(
String.format("acloud errors: %s", errors), mDeviceDescriptor);
}
}
mGceAvdInfo =
GceAvdInfo.parseGceInfoFromFile(
reportFile, mDeviceDescriptor, mDeviceOptions.getRemoteAdbPort());
return mGceAvdInfo;
} catch (IOException e) {
throw new TargetSetupError("failed to create log file", e, mDeviceDescriptor);
} finally {
FileUtil.deleteFile(reportFile);
}
}
/**
* Retrieve the instance name from the gce boot logs. Search for the 'name': 'gce-<name>'
* pattern to extract the name of it. We extract from the logs instead of result file because on
* gce boot failure, the attempted instance name won't show in json.
*/
protected String extractInstanceName(String bootupLogs) {
if (bootupLogs != null) {
final String pattern = "'name': u?'(((gce-)|(ins-))(.*?))'";
Pattern namePattern = Pattern.compile(pattern);
Matcher matcher = namePattern.matcher(bootupLogs);
if (matcher.find()) {
return matcher.group(1);
}
}
return null;
}
/** Build and return the command to launch GCE. Exposed for testing. */
protected List<String> buildGceCmd(File reportFile, IBuildInfo b, String ipDevice) {
File avdDriverFile = getTestDeviceOptions().getAvdDriverBinary();
if (!avdDriverFile.exists()) {
throw new RuntimeException(
String.format(
"Could not find the Acloud driver at %s",
avdDriverFile.getAbsolutePath()));
}
if (!avdDriverFile.canExecute()) {
// Set the executable bit if needed
FileUtil.chmodGroupRWX(avdDriverFile);
}
List<String> gceArgs = ArrayUtil.list(avdDriverFile.getAbsolutePath());
gceArgs.add(
TestDeviceOptions.getCreateCommandByInstanceType(
getTestDeviceOptions().getInstanceType()));
// Handle the build id related params
List<String> gceDriverParams = getTestDeviceOptions().getGceDriverParams();
if (TestDeviceOptions.InstanceType.CHEEPS.equals(
getTestDeviceOptions().getInstanceType())) {
gceArgs.add("--avd-type");
gceArgs.add("cheeps");
if (getTestDeviceOptions().getCrosUser() != null
&& getTestDeviceOptions().getCrosPassword() != null) {
gceArgs.add("--user");
gceArgs.add(getTestDeviceOptions().getCrosUser());
gceArgs.add("--password");
gceArgs.add(getTestDeviceOptions().getCrosPassword());
}
}
// If args passed by gce-driver-param do not contain build_id or branch,
// use build_id and branch from device BuildInfo
if (!gceDriverParams.contains("--build_id") && !gceDriverParams.contains("--branch")) {
gceArgs.add("--build_target");
if (b.getBuildAttributes().containsKey("build_target")) {
// If BuildInfo contains the attribute for a build target, use that.
gceArgs.add(b.getBuildAttributes().get("build_target"));
} else {
gceArgs.add(b.getBuildFlavor());
}
gceArgs.add("--branch");
gceArgs.add(b.getBuildBranch());
gceArgs.add("--build_id");
gceArgs.add(b.getBuildId());
}
// Add additional args passed by gce-driver-param.
gceArgs.addAll(gceDriverParams);
// Get extra params by instance type
gceArgs.addAll(
TestDeviceOptions.getExtraParamsByInstanceType(
getTestDeviceOptions().getInstanceType(),
getTestDeviceOptions().getBaseImage()));
if (ipDevice == null) {
gceArgs.add("--config_file");
gceArgs.add(getAvdConfigFile().getAbsolutePath());
if (getTestDeviceOptions().getServiceAccountJsonKeyFile() != null) {
gceArgs.add("--service_account_json_private_key_path");
gceArgs.add(
getTestDeviceOptions().getServiceAccountJsonKeyFile().getAbsolutePath());
}
} else {
gceArgs.add("--host");
gceArgs.add(ipDevice);
}
gceArgs.add("--report_file");
gceArgs.add(reportFile.getAbsolutePath());
switch (getTestDeviceOptions().getGceDriverLogLevel()) {
case DEBUG:
gceArgs.add("-v");
break;
case VERBOSE:
gceArgs.add("-vv");
break;
default:
break;
}
if (getTestDeviceOptions().getGceAccount() != null) {
gceArgs.add("--email");
gceArgs.add(getTestDeviceOptions().getGceAccount());
}
// Do not pass flags --logcat_file and --serial_log_file to collect logcat and serial logs.
return gceArgs;
}
/**
* Shutdown the Gce instance associated with the {@link #startGce()}.
*
* @return returns true if gce shutdown was requested as non-blocking.
*/
public boolean shutdownGce() {
if (!getTestDeviceOptions().getAvdDriverBinary().canExecute()) {
mGceAvdInfo = null;
throw new RuntimeException(
String.format(
"GCE launcher %s is invalid",
getTestDeviceOptions().getAvdDriverBinary()));
}
if (mGceAvdInfo == null) {
CLog.d("No instance to shutdown.");
return false;
}
try {
boolean res =
AcloudShutdown(
getTestDeviceOptions(), getRunUtil(), mGceAvdInfo.instanceName());
if (res) {
mBuildInfo.addBuildAttribute(GCE_INSTANCE_CLEANED_KEY, "true");
}
return res;
} finally {
mGceAvdInfo = null;
}
}
/**
* Actual Acloud run to shutdown the virtual device.
*
* @param options The {@link TestDeviceOptions} for the Acloud options
* @param runUtil The {@link IRunUtil} to run Acloud
* @param instanceName The instance to shutdown.
* @return True if successful
*/
public static boolean AcloudShutdown(
TestDeviceOptions options, IRunUtil runUtil, String instanceName) {
List<String> gceArgs = ArrayUtil.list(options.getAvdDriverBinary().getAbsolutePath());
gceArgs.add("delete");
// Add extra args.
File f = null;
File config = null;
try {
config = FileUtil.createTempFile(options.getAvdConfigFile().getName(), "config");
gceArgs.add("--instance_names");
gceArgs.add(instanceName);
gceArgs.add("--config_file");
// Copy the config in case it comes from a dynamic file. In order to ensure Acloud has
// the file until it's done with it.
FileUtil.copyFile(options.getAvdConfigFile(), config);
gceArgs.add(config.getAbsolutePath());
if (options.getServiceAccountJsonKeyFile() != null) {
gceArgs.add("--service_account_json_private_key_path");
gceArgs.add(options.getServiceAccountJsonKeyFile().getAbsolutePath());
}
f = FileUtil.createTempFile("gce_avd_driver", ".json");
gceArgs.add("--report_file");
gceArgs.add(f.getAbsolutePath());
CLog.i("Tear down of GCE with %s", gceArgs.toString());
if (options.waitForGceTearDown()) {
CommandResult cmd =
runUtil.runTimedCmd(
options.getGceCmdTimeout(),
gceArgs.toArray(new String[gceArgs.size()]));
FileUtil.deleteFile(config);
if (!CommandStatus.SUCCESS.equals(cmd.getStatus())) {
CLog.w(
"Failed to tear down GCE %s with the following arg: %s."
+ "\nstdout:%s\nstderr:%s",
instanceName, gceArgs, cmd.getStdout(), cmd.getStderr());
return false;
}
} else {
// Discard the output so the process is not linked to the parent and doesn't die
// if the JVM exit.
Process p = runUtil.runCmdInBackground(Redirect.DISCARD, gceArgs);
AcloudDeleteCleaner cleaner = new AcloudDeleteCleaner(p, config);
cleaner.start();
}
} catch (IOException | RuntimeException e) {
CLog.e("failed to create log file for GCE Teardown");
CLog.e(e);
FileUtil.deleteFile(config);
return false;
} finally {
FileUtil.deleteFile(f);
}
return true;
}
/**
* Get a bugreportz from the device using ssh to avoid any adb connection potential issue.
*
* @param gceAvd The {@link GceAvdInfo} that describe the device.
* @param options a {@link TestDeviceOptions} describing the device options to be used for the
* GCE device.
* @param runUtil a {@link IRunUtil} to execute commands.
* @return A file pointing to the zip bugreport, or null if an issue occurred.
* @throws IOException
*/
public static File getBugreportzWithSsh(
GceAvdInfo gceAvd, TestDeviceOptions options, IRunUtil runUtil) throws IOException {
String output = remoteSshCommandExec(gceAvd, options, runUtil, "bugreportz");
Matcher match = BUGREPORTZ_RESPONSE_PATTERN.matcher(output);
if (!match.find()) {
CLog.e("Something went wrong during bugreportz collection: '%s'", output);
return null;
}
String remoteFilePath = match.group(2);
File localTmpFile = FileUtil.createTempFile("bugreport-ssh", ".zip");
if (!RemoteFileUtil.fetchRemoteFile(
gceAvd, options, runUtil, REMOTE_FILE_OP_TIMEOUT, remoteFilePath, localTmpFile)) {
FileUtil.deleteFile(localTmpFile);
return null;
}
return localTmpFile;
}
/**
* Get a bugreport via ssh for a nested instance. This requires requesting the adb in the nested
* virtual instance.
*
* @param gceAvd The {@link GceAvdInfo} that describe the device.
* @param options a {@link TestDeviceOptions} describing the device options to be used for the
* GCE device.
* @param runUtil a {@link IRunUtil} to execute commands.
* @return A file pointing to the zip bugreport, or null if an issue occurred.
* @throws IOException
*/
public static File getNestedDeviceSshBugreportz(
GceAvdInfo gceAvd, TestDeviceOptions options, IRunUtil runUtil) throws IOException {
if (gceAvd == null || gceAvd.hostAndPort() == null) {
return null;
}
String output =
remoteSshCommandExec(
gceAvd,
options,
runUtil,
"./bin/adb",
"wait-for-device",
"shell",
"bugreportz");
Matcher match = BUGREPORTZ_RESPONSE_PATTERN.matcher(output);
if (!match.find()) {
CLog.e("Something went wrong during bugreportz collection: '%s'", output);
return null;
}
String deviceFilePath = match.group(2);
String pullOutput =
remoteSshCommandExec(gceAvd, options, runUtil, "./bin/adb", "pull", deviceFilePath);
CLog.d(pullOutput);
String remoteFilePath = "./" + new File(deviceFilePath).getName();
File localTmpFile = FileUtil.createTempFile("bugreport-ssh", ".zip");
if (!RemoteFileUtil.fetchRemoteFile(
gceAvd, options, runUtil, REMOTE_FILE_OP_TIMEOUT, remoteFilePath, localTmpFile)) {
FileUtil.deleteFile(localTmpFile);
return null;
}
return localTmpFile;
}
/**
* Fetch a remote file from a nested instance and log it.
*
* @param logger The {@link ITestLogger} where to log the file.
* @param gceAvd The {@link GceAvdInfo} that describe the device.
* @param options a {@link TestDeviceOptions} describing the device options to be used for the
* GCE device.
* @param runUtil a {@link IRunUtil} to execute commands.
* @param remoteFilePath The remote path where to find the file.
* @param type the {@link LogDataType} of the logged file.
*/
public static void logNestedRemoteFile(
ITestLogger logger,
GceAvdInfo gceAvd,
TestDeviceOptions options,
IRunUtil runUtil,
String remoteFilePath,
LogDataType type) {
logNestedRemoteFile(logger, gceAvd, options, runUtil, remoteFilePath, type, null);
}
/**
* Fetch a remote file from a nested instance and log it.
*
* @param logger The {@link ITestLogger} where to log the file.
* @param gceAvd The {@link GceAvdInfo} that describe the device.
* @param options a {@link TestDeviceOptions} describing the device options to be used for the
* GCE device.
* @param runUtil a {@link IRunUtil} to execute commands.
* @param remoteFilePath The remote path where to find the file.
* @param type the {@link LogDataType} of the logged file.
* @param baseName The base name to use to log the file. If null the actual file name will be
* used.
*/
public static void logNestedRemoteFile(
ITestLogger logger,
GceAvdInfo gceAvd,
TestDeviceOptions options,
IRunUtil runUtil,
String remoteFilePath,
LogDataType type,
String baseName) {
File remoteFile =
RemoteFileUtil.fetchRemoteFile(
gceAvd, options, runUtil, REMOTE_FILE_OP_TIMEOUT, remoteFilePath);
if (remoteFile != null) {
// If we happened to fetch a directory, log all the subfiles
logFile(remoteFile, baseName, logger, type);
}
}
private static void logFile(
File remoteFile, String baseName, ITestLogger logger, LogDataType type) {
if (remoteFile.isDirectory()) {
for (File f : remoteFile.listFiles()) {
logFile(f, null, logger, type);
}
} else {
try (InputStreamSource remoteFileStream = new FileInputStreamSource(remoteFile, true)) {
String name = baseName;
if (name == null) {
name = remoteFile.getName();
}
logger.testLog(name, type, remoteFileStream);
}
}
}
/**
* Execute the remote command via ssh on an instance.
*
* @param gceAvd The {@link GceAvdInfo} that describe the device.
* @param options a {@link TestDeviceOptions} describing the device options to be used for the
* GCE device.
* @param runUtil a {@link IRunUtil} to execute commands.
* @param timeoutMs The timeout in millisecond for the command. 0 means no timeout.
* @param command The remote command to execute.
* @return {@link CommandResult} containing the result of the execution.
*/
public static CommandResult remoteSshCommandExecution(
GceAvdInfo gceAvd,
TestDeviceOptions options,
IRunUtil runUtil,
long timeoutMs,
String... command) {
return RemoteSshUtil.remoteSshCommandExec(gceAvd, options, runUtil, timeoutMs, command);
}
private static String remoteSshCommandExec(
GceAvdInfo gceAvd, TestDeviceOptions options, IRunUtil runUtil, String... command) {
CommandResult res =
remoteSshCommandExecution(gceAvd, options, runUtil, BUGREPORT_TIMEOUT, command);
// We attempt to get a clean output from our command
String output = res.getStdout().trim();
if (!CommandStatus.SUCCESS.equals(res.getStatus())) {
CLog.e("issue when attempting to execute '%s':", Arrays.asList(command));
CLog.e("Stderr: %s", res.getStderr());
} else if (output.isEmpty()) {
CLog.e("Stdout from '%s' was empty", Arrays.asList(command));
CLog.e("Stderr: %s", res.getStderr());
}
return output;
}
/**
* Reads the current content of the Gce Avd instance serial log.
*
* @param infos The {@link GceAvdInfo} describing the instance.
* @param avdConfigFile the avd config file
* @param jsonKeyFile the service account json key file.
* @param runUtil a {@link IRunUtil} to execute commands.
* @return The serial log output or null if something goes wrong.
*/
public static String getInstanceSerialLog(
GceAvdInfo infos, File avdConfigFile, File jsonKeyFile, IRunUtil runUtil) {
AcloudConfigParser config = AcloudConfigParser.parseConfig(avdConfigFile);
if (config == null) {
CLog.e("Failed to parse our acloud config.");
return null;
}
if (infos == null) {
return null;
}
try {
Credential credential = createCredential(config, jsonKeyFile);
String project = config.getValueForKey(AcloudKeys.PROJECT);
String zone = config.getValueForKey(AcloudKeys.ZONE);
String instanceName = infos.instanceName();
Compute compute =
new Compute.Builder(
GoogleNetHttpTransport.newTrustedTransport(),
JSON_FACTORY,
null)
.setApplicationName(project)
.setHttpRequestInitializer(credential)
.build();
GetSerialPortOutput outputPort =
compute.instances().getSerialPortOutput(project, zone, instanceName);
SerialPortOutput output = outputPort.execute();
return output.getContents();
} catch (GeneralSecurityException | IOException e) {
CLog.e(e);
return null;
}
}
private static Credential createCredential(AcloudConfigParser config, File jsonKeyFile)
throws GeneralSecurityException, IOException {
if (jsonKeyFile != null) {
return GoogleApiClientUtil.createCredentialFromJsonKeyFile(jsonKeyFile, SCOPES);
} else if (config.getValueForKey(AcloudKeys.SERVICE_ACCOUNT_JSON_PRIVATE_KEY) != null) {
jsonKeyFile =
new File(config.getValueForKey(AcloudKeys.SERVICE_ACCOUNT_JSON_PRIVATE_KEY));
return GoogleApiClientUtil.createCredentialFromJsonKeyFile(jsonKeyFile, SCOPES);
} else {
String serviceAccount = config.getValueForKey(AcloudKeys.SERVICE_ACCOUNT_NAME);
String serviceKey = config.getValueForKey(AcloudKeys.SERVICE_ACCOUNT_PRIVATE_KEY);
return GoogleApiClientUtil.createCredentialFromP12File(
serviceAccount, new File(serviceKey), SCOPES);
}
}
public void cleanUp() {
// Clean up logs file if any was created.
}
/** Returns the instance of the {@link IRunUtil}. */
@VisibleForTesting
IRunUtil getRunUtil() {
return RunUtil.getDefault();
}
/**
* Log the serial output of a device described by {@link GceAvdInfo}.
*
* @param infos The {@link GceAvdInfo} describing the instance.
* @param logger The {@link ITestLogger} where to log the serial log.
*/
public void logSerialOutput(GceAvdInfo infos, ITestLogger logger) {
String output =
GceManager.getInstanceSerialLog(
infos,
getAvdConfigFile(),
getTestDeviceOptions().getServiceAccountJsonKeyFile(),
getRunUtil());
if (output == null) {
CLog.w("Failed to collect the instance serial logs.");
return;
}
try (ByteArrayInputStreamSource source =
new ByteArrayInputStreamSource(output.getBytes())) {
logger.testLog("gce_full_serial_log", LogDataType.TEXT, source);
}
}
/** Log the information related to the stable host image used. */
public void logStableHostImageInfos(IBuildInfo build) {
AcloudConfigParser config = AcloudConfigParser.parseConfig(getAvdConfigFile());
if (config == null) {
CLog.e("Failed to parse our acloud config.");
return;
}
if (build == null) {
return;
}
if (config.getValueForKey(AcloudKeys.STABLE_HOST_IMAGE_NAME) != null) {
build.addBuildAttribute(
AcloudKeys.STABLE_HOST_IMAGE_NAME.toString(),
config.getValueForKey(AcloudKeys.STABLE_HOST_IMAGE_NAME));
}
if (config.getValueForKey(AcloudKeys.STABLE_HOST_IMAGE_PROJECT) != null) {
build.addBuildAttribute(
AcloudKeys.STABLE_HOST_IMAGE_PROJECT.toString(),
config.getValueForKey(AcloudKeys.STABLE_HOST_IMAGE_PROJECT));
}
}
/**
* Returns the {@link TestDeviceOptions} associated with the device that the gce manager was
* initialized with.
*/
private TestDeviceOptions getTestDeviceOptions() {
return mDeviceOptions;
}
@VisibleForTesting
File getAvdConfigFile() {
return getTestDeviceOptions().getAvdConfigFile();
}
/**
* Thread that helps cleaning the copied config when the process is done. This ensures acloud is
* not missing its config until its done.
*/
private static class AcloudDeleteCleaner extends Thread {
private Process mProcess;
private File mConfigFile;
public AcloudDeleteCleaner(Process p, File config) {
setDaemon(true);
setName("acloud-delete-cleaner");
mProcess = p;
mConfigFile = config;
}
@Override
public void run() {
try {
mProcess.waitFor();
} catch (InterruptedException e) {
CLog.e(e);
}
FileUtil.deleteFile(mConfigFile);
}
}
}