• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.device.cloud;
17 
18 import com.android.tradefed.build.BuildInfo;
19 import com.android.tradefed.build.IBuildInfo;
20 import com.android.tradefed.command.remote.DeviceDescriptor;
21 import com.android.tradefed.device.TestDeviceOptions;
22 import com.android.tradefed.device.cloud.AcloudConfigParser.AcloudKeys;
23 import com.android.tradefed.device.cloud.GceAvdInfo.GceStatus;
24 import com.android.tradefed.log.ITestLogger;
25 import com.android.tradefed.log.LogUtil.CLog;
26 import com.android.tradefed.result.ByteArrayInputStreamSource;
27 import com.android.tradefed.result.FileInputStreamSource;
28 import com.android.tradefed.result.InputStreamSource;
29 import com.android.tradefed.result.LogDataType;
30 import com.android.tradefed.targetprep.TargetSetupError;
31 import com.android.tradefed.util.ArrayUtil;
32 import com.android.tradefed.util.CommandResult;
33 import com.android.tradefed.util.CommandStatus;
34 import com.android.tradefed.util.FileUtil;
35 import com.android.tradefed.util.GoogleApiClientUtil;
36 import com.android.tradefed.util.IRunUtil;
37 import com.android.tradefed.util.RunUtil;
38 
39 import com.google.api.client.auth.oauth2.Credential;
40 import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
41 import com.google.api.client.json.JsonFactory;
42 import com.google.api.client.json.jackson2.JacksonFactory;
43 import com.google.api.services.compute.Compute;
44 import com.google.api.services.compute.Compute.Instances.GetSerialPortOutput;
45 import com.google.api.services.compute.ComputeScopes;
46 import com.google.api.services.compute.model.SerialPortOutput;
47 import com.google.common.annotations.VisibleForTesting;
48 import com.google.common.net.HostAndPort;
49 
50 import java.io.File;
51 import java.io.IOException;
52 import java.security.GeneralSecurityException;
53 import java.util.Arrays;
54 import java.util.List;
55 import java.util.regex.Matcher;
56 import java.util.regex.Pattern;
57 
58 /** Helper that manages the GCE calls to start/stop and collect logs from GCE. */
59 public class GceManager {
60     private static final long BUGREPORT_TIMEOUT = 15 * 60 * 1000L;
61     private static final long REMOTE_FILE_OP_TIMEOUT = 10 * 60 * 1000L;
62     private static final Pattern BUGREPORTZ_RESPONSE_PATTERN = Pattern.compile("(OK:)(.*)");
63     private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
64     private static final List<String> SCOPES = Arrays.asList(ComputeScopes.COMPUTE_READONLY);
65 
66     private DeviceDescriptor mDeviceDescriptor;
67     private TestDeviceOptions mDeviceOptions;
68     private IBuildInfo mBuildInfo;
69     private List<IBuildInfo> mTestResourceBuildInfos;
70 
71     private String mGceInstanceName = null;
72     private String mGceHost = null;
73     private GceAvdInfo mGceAvdInfo = null;
74 
75     /**
76      * Ctor
77      *
78      * @param deviceDesc The {@link DeviceDescriptor} that will be associated with the GCE device.
79      * @param deviceOptions A {@link TestDeviceOptions} associated with the device.
80      * @param buildInfo A {@link IBuildInfo} describing the gce build to start.
81      * @param testResourceBuildInfos A list {@link IBuildInfo} describing test resources
82      */
GceManager( DeviceDescriptor deviceDesc, TestDeviceOptions deviceOptions, IBuildInfo buildInfo, List<IBuildInfo> testResourceBuildInfos)83     public GceManager(
84             DeviceDescriptor deviceDesc,
85             TestDeviceOptions deviceOptions,
86             IBuildInfo buildInfo,
87             List<IBuildInfo> testResourceBuildInfos) {
88         mDeviceDescriptor = deviceDesc;
89         mDeviceOptions = deviceOptions;
90         mBuildInfo = buildInfo;
91         mTestResourceBuildInfos = testResourceBuildInfos;
92     }
93 
94     /**
95      * Ctor, variation that can be used to provide the GCE instance name to use directly.
96      *
97      * @param deviceDesc The {@link DeviceDescriptor} that will be associated with the GCE device.
98      * @param deviceOptions A {@link TestDeviceOptions} associated with the device
99      * @param buildInfo A {@link IBuildInfo} describing the gce build to start.
100      * @param testResourceBuildInfos A list {@link IBuildInfo} describing test resources
101      * @param gceInstanceName The instance name to use.
102      * @param gceHost The host name or ip of the instance to use.
103      */
GceManager( DeviceDescriptor deviceDesc, TestDeviceOptions deviceOptions, IBuildInfo buildInfo, List<IBuildInfo> testResourceBuildInfos, String gceInstanceName, String gceHost)104     public GceManager(
105             DeviceDescriptor deviceDesc,
106             TestDeviceOptions deviceOptions,
107             IBuildInfo buildInfo,
108             List<IBuildInfo> testResourceBuildInfos,
109             String gceInstanceName,
110             String gceHost) {
111         this(deviceDesc, deviceOptions, buildInfo, testResourceBuildInfos);
112         mGceInstanceName = gceInstanceName;
113         mGceHost = gceHost;
114     }
115 
116     /**
117      * Attempt to start a gce instance
118      *
119      * @return a {@link GceAvdInfo} describing the GCE instance. Could be a BOOT_FAIL instance.
120      * @throws TargetSetupError
121      */
startGce()122     public GceAvdInfo startGce() throws TargetSetupError {
123         mGceAvdInfo = null;
124         // For debugging purposes bypass.
125         if (mGceHost != null && mGceInstanceName != null) {
126             mGceAvdInfo =
127                     new GceAvdInfo(
128                             mGceInstanceName,
129                             HostAndPort.fromString(mGceHost)
130                                     .withDefaultPort(mDeviceOptions.getRemoteAdbPort()));
131             return mGceAvdInfo;
132         }
133         // Add extra args.
134         File reportFile = null;
135         try {
136             reportFile = FileUtil.createTempFile("gce_avd_driver", ".json");
137             List<String> gceArgs = buildGceCmd(reportFile, mBuildInfo);
138 
139             CLog.i("Launching GCE with %s", gceArgs.toString());
140             CommandResult cmd =
141                     getRunUtil()
142                             .runTimedCmd(
143                                     getTestDeviceOptions().getGceCmdTimeout(),
144                                     gceArgs.toArray(new String[gceArgs.size()]));
145             CLog.i("GCE driver stderr: %s", cmd.getStderr());
146             String instanceName = extractInstanceName(cmd.getStderr());
147             if (instanceName != null) {
148                 mBuildInfo.addBuildAttribute("gce-instance-name", instanceName);
149             } else {
150                 CLog.w("Could not extract an instance name for the gce device.");
151             }
152             if (CommandStatus.TIMED_OUT.equals(cmd.getStatus())) {
153                 String errors =
154                         String.format(
155                                 "acloud errors: timeout after %dms, " + "acloud did not return",
156                                 getTestDeviceOptions().getGceCmdTimeout());
157                 if (instanceName != null) {
158                     // If we managed to parse the instance name, report the boot failure so it
159                     // can be shutdown.
160                     mGceAvdInfo = new GceAvdInfo(instanceName, null, errors, GceStatus.BOOT_FAIL);
161                     return mGceAvdInfo;
162                 }
163                 throw new TargetSetupError(errors, mDeviceDescriptor);
164             } else if (!CommandStatus.SUCCESS.equals(cmd.getStatus())) {
165                 CLog.w("Error when booting the Gce instance, reading output of gce driver");
166                 mGceAvdInfo =
167                         GceAvdInfo.parseGceInfoFromFile(
168                                 reportFile, mDeviceDescriptor, mDeviceOptions.getRemoteAdbPort());
169                 String errors = "";
170                 if (mGceAvdInfo != null) {
171                     // We always return the GceAvdInfo describing the instance when possible
172                     // The caller can decide actions to be taken.
173                     return mGceAvdInfo;
174                 } else {
175                     errors =
176                             "Could not get a valid instance name, check the gce driver's output."
177                                     + "The instance may not have booted up at all.";
178                     CLog.e(errors);
179                     throw new TargetSetupError(
180                             String.format("acloud errors: %s", errors), mDeviceDescriptor);
181                 }
182             }
183             mGceAvdInfo =
184                     GceAvdInfo.parseGceInfoFromFile(
185                             reportFile, mDeviceDescriptor, mDeviceOptions.getRemoteAdbPort());
186             return mGceAvdInfo;
187         } catch (IOException e) {
188             throw new TargetSetupError("failed to create log file", e, mDeviceDescriptor);
189         } finally {
190             FileUtil.deleteFile(reportFile);
191         }
192     }
193 
194     /**
195      * Retrieve the instance name from the gce boot logs. Search for the 'name': 'gce-<name>'
196      * pattern to extract the name of it. We extract from the logs instead of result file because on
197      * gce boot failure, the attempted instance name won't show in json.
198      */
extractInstanceName(String bootupLogs)199     protected String extractInstanceName(String bootupLogs) {
200         if (bootupLogs != null) {
201             final String pattern = "'name': '(((gce-)|(ins-))(.*?))'";
202             Pattern namePattern = Pattern.compile(pattern);
203             Matcher matcher = namePattern.matcher(bootupLogs);
204             if (matcher.find()) {
205                 return matcher.group(1);
206             }
207         }
208         return null;
209     }
210 
211     /** Build and return the command to launch GCE. Exposed for testing. */
buildGceCmd(File reportFile, IBuildInfo b)212     protected List<String> buildGceCmd(File reportFile, IBuildInfo b) {
213         List<String> gceArgs =
214                 ArrayUtil.list(getTestDeviceOptions().getAvdDriverBinary().getAbsolutePath());
215         gceArgs.add(
216                 TestDeviceOptions.getCreateCommandByInstanceType(
217                         getTestDeviceOptions().getInstanceType()));
218         // Handle the build id related params
219         List<String> gceDriverParams = getTestDeviceOptions().getGceDriverParams();
220 
221         if (TestDeviceOptions.InstanceType.CHEEPS.equals(
222                 getTestDeviceOptions().getInstanceType())) {
223             gceArgs.add("--avd-type");
224             gceArgs.add("cheeps");
225         }
226 
227         // If args passed by gce-driver-param do not contain build_id or branch,
228         // use build_id and branch from device BuildInfo
229         if (!gceDriverParams.contains("--build_id") && !gceDriverParams.contains("--branch")) {
230             gceArgs.add("--build_target");
231             if (b.getBuildAttributes().containsKey("build_target")) {
232                 // If BuildInfo contains the attribute for a build target, use that.
233                 gceArgs.add(b.getBuildAttributes().get("build_target"));
234             } else {
235                 gceArgs.add(b.getBuildFlavor());
236             }
237             gceArgs.add("--branch");
238             gceArgs.add(b.getBuildBranch());
239             gceArgs.add("--build_id");
240             gceArgs.add(b.getBuildId());
241         }
242         // Add additional args passed by gce-driver-param.
243         gceArgs.addAll(gceDriverParams);
244         // Get extra params by instance type
245         gceArgs.addAll(
246                 TestDeviceOptions.getExtraParamsByInstanceType(
247                         getTestDeviceOptions().getInstanceType(),
248                         getTestDeviceOptions().getBaseImage()));
249         gceArgs.add("--config_file");
250         gceArgs.add(getAvdConfigFile().getAbsolutePath());
251         if (getTestDeviceOptions().getSerivceAccountJsonKeyFile() != null) {
252             gceArgs.add("--service_account_json_private_key_path");
253             gceArgs.add(getTestDeviceOptions().getSerivceAccountJsonKeyFile().getAbsolutePath());
254         }
255         gceArgs.add("--report_file");
256         gceArgs.add(reportFile.getAbsolutePath());
257         switch (getTestDeviceOptions().getGceDriverLogLevel()) {
258             case DEBUG:
259                 gceArgs.add("-v");
260                 break;
261             case VERBOSE:
262                 gceArgs.add("-vv");
263                 break;
264             default:
265                 break;
266         }
267         if (getTestDeviceOptions().getGceAccount() != null) {
268             gceArgs.add("--email");
269             gceArgs.add(getTestDeviceOptions().getGceAccount());
270         }
271         // Do not pass flags --logcat_file and --serial_log_file to collect logcat and serial logs.
272 
273         return gceArgs;
274     }
275 
276     /** Shutdown the Gce instance associated with the {@link #startGce()}. */
shutdownGce()277     public void shutdownGce() {
278         if (!getTestDeviceOptions().getAvdDriverBinary().canExecute()) {
279             mGceAvdInfo = null;
280             throw new RuntimeException(
281                     String.format(
282                             "GCE launcher %s is invalid",
283                             getTestDeviceOptions().getAvdDriverBinary()));
284         }
285         if (mGceAvdInfo == null) {
286             CLog.d("No instance to shutdown.");
287             return;
288         }
289         List<String> gceArgs =
290                 ArrayUtil.list(getTestDeviceOptions().getAvdDriverBinary().getAbsolutePath());
291         gceArgs.add("delete");
292         // Add extra args.
293         File f = null;
294         try {
295             gceArgs.add("--instance_names");
296             gceArgs.add(mGceAvdInfo.instanceName());
297             gceArgs.add("--config_file");
298             gceArgs.add(getAvdConfigFile().getAbsolutePath());
299             if (getTestDeviceOptions().getSerivceAccountJsonKeyFile() != null) {
300                 gceArgs.add("--service_account_json_private_key_path");
301                 gceArgs.add(
302                         getTestDeviceOptions().getSerivceAccountJsonKeyFile().getAbsolutePath());
303             }
304             f = FileUtil.createTempFile("gce_avd_driver", ".json");
305             gceArgs.add("--report_file");
306             gceArgs.add(f.getAbsolutePath());
307             CLog.i("Tear down of GCE with %s", gceArgs.toString());
308             if (getTestDeviceOptions().waitForGceTearDown()) {
309                 CommandResult cmd =
310                         getRunUtil()
311                                 .runTimedCmd(
312                                         getTestDeviceOptions().getGceCmdTimeout(),
313                                         gceArgs.toArray(new String[gceArgs.size()]));
314                 if (!CommandStatus.SUCCESS.equals(cmd.getStatus())) {
315                     CLog.w(
316                             "Failed to tear down GCE %s with the following arg, %s",
317                             mGceAvdInfo.instanceName(), gceArgs);
318                 }
319             } else {
320                 getRunUtil().runCmdInBackground(gceArgs.toArray(new String[gceArgs.size()]));
321             }
322         } catch (IOException e) {
323             CLog.e("failed to create log file for GCE Teardown");
324             CLog.e(e);
325         } finally {
326             FileUtil.deleteFile(f);
327             mGceAvdInfo = null;
328         }
329     }
330 
331     /**
332      * Get a bugreportz from the device using ssh to avoid any adb connection potential issue.
333      *
334      * @param gceAvd The {@link GceAvdInfo} that describe the device.
335      * @param options a {@link TestDeviceOptions} describing the device options to be used for the
336      *     GCE device.
337      * @param runUtil a {@link IRunUtil} to execute commands.
338      * @return A file pointing to the zip bugreport, or null if an issue occurred.
339      * @throws IOException
340      */
getBugreportzWithSsh( GceAvdInfo gceAvd, TestDeviceOptions options, IRunUtil runUtil)341     public static File getBugreportzWithSsh(
342             GceAvdInfo gceAvd, TestDeviceOptions options, IRunUtil runUtil) throws IOException {
343         String output = remoteSshCommandExec(gceAvd, options, runUtil, "bugreportz");
344         Matcher match = BUGREPORTZ_RESPONSE_PATTERN.matcher(output);
345         if (!match.find()) {
346             CLog.e("Something went wrong during bugreportz collection: '%s'", output);
347             return null;
348         }
349         String remoteFilePath = match.group(2);
350         File localTmpFile = FileUtil.createTempFile("bugreport-ssh", ".zip");
351         if (!RemoteFileUtil.fetchRemoteFile(
352                 gceAvd, options, runUtil, REMOTE_FILE_OP_TIMEOUT, remoteFilePath, localTmpFile)) {
353             FileUtil.deleteFile(localTmpFile);
354             return null;
355         }
356         return localTmpFile;
357     }
358 
359     /**
360      * Get a bugreport via ssh for a nested instance. This requires requesting the adb in the nested
361      * virtual instance.
362      *
363      * @param gceAvd The {@link GceAvdInfo} that describe the device.
364      * @param options a {@link TestDeviceOptions} describing the device options to be used for the
365      *     GCE device.
366      * @param runUtil a {@link IRunUtil} to execute commands.
367      * @return A file pointing to the zip bugreport, or null if an issue occurred.
368      * @throws IOException
369      */
getNestedDeviceSshBugreportz( GceAvdInfo gceAvd, TestDeviceOptions options, IRunUtil runUtil)370     public static File getNestedDeviceSshBugreportz(
371             GceAvdInfo gceAvd, TestDeviceOptions options, IRunUtil runUtil) throws IOException {
372         String output = "";
373         // Retry a couple of time because adb might not be started for that user.
374         // FIXME: See if we can use vsoc-01 directly to avoid this
375         for (int i = 0; i < 3; i++) {
376             output = remoteSshCommandExec(gceAvd, options, runUtil, "adb", "shell", "bugreportz");
377             Matcher match = BUGREPORTZ_RESPONSE_PATTERN.matcher(output);
378             if (match.find()) {
379                 break;
380             }
381         }
382         Matcher match = BUGREPORTZ_RESPONSE_PATTERN.matcher(output);
383         if (!match.find()) {
384             CLog.e("Something went wrong during bugreportz collection: '%s'", output);
385             return null;
386         }
387         String deviceFilePath = match.group(2);
388         String pullOutput =
389                 remoteSshCommandExec(gceAvd, options, runUtil, "adb", "pull", deviceFilePath);
390         CLog.d(pullOutput);
391         String remoteFilePath = "./" + new File(deviceFilePath).getName();
392         File localTmpFile = FileUtil.createTempFile("bugreport-ssh", ".zip");
393         if (!RemoteFileUtil.fetchRemoteFile(
394                 gceAvd, options, runUtil, REMOTE_FILE_OP_TIMEOUT, remoteFilePath, localTmpFile)) {
395             FileUtil.deleteFile(localTmpFile);
396             return null;
397         }
398         return localTmpFile;
399     }
400 
401     /**
402      * Fetch a remote file from a nested instance and log it.
403      *
404      * @param logger The {@link ITestLogger} where to log the file.
405      * @param gceAvd The {@link GceAvdInfo} that describe the device.
406      * @param options a {@link TestDeviceOptions} describing the device options to be used for the
407      *     GCE device.
408      * @param runUtil a {@link IRunUtil} to execute commands.
409      * @param remoteFilePath The remote path where to find the file.
410      * @param type the {@link LogDataType} of the logged file.
411      */
logNestedRemoteFile( ITestLogger logger, GceAvdInfo gceAvd, TestDeviceOptions options, IRunUtil runUtil, String remoteFilePath, LogDataType type)412     public static void logNestedRemoteFile(
413             ITestLogger logger,
414             GceAvdInfo gceAvd,
415             TestDeviceOptions options,
416             IRunUtil runUtil,
417             String remoteFilePath,
418             LogDataType type) {
419         logNestedRemoteFile(logger, gceAvd, options, runUtil, remoteFilePath, type, null);
420     }
421 
422     /**
423      * Fetch a remote file from a nested instance and log it.
424      *
425      * @param logger The {@link ITestLogger} where to log the file.
426      * @param gceAvd The {@link GceAvdInfo} that describe the device.
427      * @param options a {@link TestDeviceOptions} describing the device options to be used for the
428      *     GCE device.
429      * @param runUtil a {@link IRunUtil} to execute commands.
430      * @param remoteFilePath The remote path where to find the file.
431      * @param type the {@link LogDataType} of the logged file.
432      * @param baseName The base name to use to log the file. If null the actual file name will be
433      *     used.
434      */
logNestedRemoteFile( ITestLogger logger, GceAvdInfo gceAvd, TestDeviceOptions options, IRunUtil runUtil, String remoteFilePath, LogDataType type, String baseName)435     public static void logNestedRemoteFile(
436             ITestLogger logger,
437             GceAvdInfo gceAvd,
438             TestDeviceOptions options,
439             IRunUtil runUtil,
440             String remoteFilePath,
441             LogDataType type,
442             String baseName) {
443         File remoteFile =
444                 RemoteFileUtil.fetchRemoteFile(
445                         gceAvd, options, runUtil, REMOTE_FILE_OP_TIMEOUT, remoteFilePath);
446         if (remoteFile != null) {
447             // If we happened to fetch a directory, log all the subfiles
448             logFile(remoteFile, baseName, logger, type);
449         }
450     }
451 
logFile( File remoteFile, String baseName, ITestLogger logger, LogDataType type)452     private static void logFile(
453             File remoteFile, String baseName, ITestLogger logger, LogDataType type) {
454         if (remoteFile.isDirectory()) {
455             for (File f : remoteFile.listFiles()) {
456                 logFile(f, null, logger, type);
457             }
458         } else {
459             try (InputStreamSource remoteFileStream = new FileInputStreamSource(remoteFile, true)) {
460                 String name = baseName;
461                 if (name == null) {
462                     name = remoteFile.getName();
463                 }
464                 logger.testLog(name, type, remoteFileStream);
465             }
466         }
467     }
468 
469     /**
470      * Execute the remote command via ssh on an instance.
471      *
472      * @param gceAvd The {@link GceAvdInfo} that describe the device.
473      * @param options a {@link TestDeviceOptions} describing the device options to be used for the
474      *     GCE device.
475      * @param runUtil a {@link IRunUtil} to execute commands.
476      * @param timeoutMs The timeout in millisecond for the command. 0 means no timeout.
477      * @param command The remote command to execute.
478      * @return {@link CommandResult} containing the result of the execution.
479      */
remoteSshCommandExecution( GceAvdInfo gceAvd, TestDeviceOptions options, IRunUtil runUtil, long timeoutMs, String... command)480     public static CommandResult remoteSshCommandExecution(
481             GceAvdInfo gceAvd,
482             TestDeviceOptions options,
483             IRunUtil runUtil,
484             long timeoutMs,
485             String... command) {
486         return RemoteSshUtil.remoteSshCommandExec(gceAvd, options, runUtil, timeoutMs, command);
487     }
488 
remoteSshCommandExec( GceAvdInfo gceAvd, TestDeviceOptions options, IRunUtil runUtil, String... command)489     private static String remoteSshCommandExec(
490             GceAvdInfo gceAvd, TestDeviceOptions options, IRunUtil runUtil, String... command) {
491         CommandResult res =
492                 remoteSshCommandExecution(gceAvd, options, runUtil, BUGREPORT_TIMEOUT, command);
493         if (!CommandStatus.SUCCESS.equals(res.getStatus())) {
494             CLog.e("issue when attempting to execute '%s':", Arrays.asList(command));
495             CLog.e("%s", res.getStderr());
496         }
497         // We attempt to get a clean output from our command
498         String output = res.getStdout().trim();
499         return output;
500     }
501 
502     /**
503      * Reads the current content of the Gce Avd instance serial log.
504      *
505      * @param infos The {@link GceAvdInfo} describing the instance.
506      * @param avdConfigFile the avd config file
507      * @param jsonKeyFile the service account json key file.
508      * @param runUtil a {@link IRunUtil} to execute commands.
509      * @return The serial log output or null if something goes wrong.
510      */
getInstanceSerialLog( GceAvdInfo infos, File avdConfigFile, File jsonKeyFile, IRunUtil runUtil)511     public static String getInstanceSerialLog(
512             GceAvdInfo infos, File avdConfigFile, File jsonKeyFile, IRunUtil runUtil) {
513         AcloudConfigParser config = AcloudConfigParser.parseConfig(avdConfigFile);
514         if (config == null) {
515             CLog.e("Failed to parse our acloud config.");
516             return null;
517         }
518         if (infos == null) {
519             return null;
520         }
521         try {
522             Credential credential = createCredential(config, jsonKeyFile);
523             String project = config.getValueForKey(AcloudKeys.PROJECT);
524             String zone = config.getValueForKey(AcloudKeys.ZONE);
525             String instanceName = infos.instanceName();
526             Compute compute =
527                     new Compute.Builder(
528                                     GoogleNetHttpTransport.newTrustedTransport(),
529                                     JSON_FACTORY,
530                                     null)
531                             .setApplicationName(project)
532                             .setHttpRequestInitializer(credential)
533                             .build();
534             GetSerialPortOutput outputPort =
535                     compute.instances().getSerialPortOutput(project, zone, instanceName);
536             SerialPortOutput output = outputPort.execute();
537             return output.getContents();
538         } catch (GeneralSecurityException | IOException e) {
539             CLog.e(e);
540             return null;
541         }
542     }
543 
createCredential(AcloudConfigParser config, File jsonKeyFile)544     private static Credential createCredential(AcloudConfigParser config, File jsonKeyFile)
545             throws GeneralSecurityException, IOException {
546         if (jsonKeyFile != null) {
547             return GoogleApiClientUtil.createCredentialFromJsonKeyFile(jsonKeyFile, SCOPES);
548         } else if (config.getValueForKey(AcloudKeys.SERVICE_ACCOUNT_JSON_PRIVATE_KEY) != null) {
549             jsonKeyFile =
550                     new File(config.getValueForKey(AcloudKeys.SERVICE_ACCOUNT_JSON_PRIVATE_KEY));
551             return GoogleApiClientUtil.createCredentialFromJsonKeyFile(jsonKeyFile, SCOPES);
552         } else {
553             String serviceAccount = config.getValueForKey(AcloudKeys.SERVICE_ACCOUNT_NAME);
554             String serviceKey = config.getValueForKey(AcloudKeys.SERVICE_ACCOUNT_PRIVATE_KEY);
555             return GoogleApiClientUtil.createCredentialFromP12File(
556                     serviceAccount, new File(serviceKey), SCOPES);
557         }
558     }
559 
cleanUp()560     public void cleanUp() {
561         // Clean up logs file if any was created.
562     }
563 
564     /** Returns the instance of the {@link IRunUtil}. */
565     @VisibleForTesting
getRunUtil()566     IRunUtil getRunUtil() {
567         return RunUtil.getDefault();
568     }
569 
570     /**
571      * Log the serial output of a device described by {@link GceAvdInfo}.
572      *
573      * @param infos The {@link GceAvdInfo} describing the instance.
574      * @param logger The {@link ITestLogger} where to log the serial log.
575      */
logSerialOutput(GceAvdInfo infos, ITestLogger logger)576     public void logSerialOutput(GceAvdInfo infos, ITestLogger logger) {
577         String output =
578                 GceManager.getInstanceSerialLog(
579                         infos,
580                         getAvdConfigFile(),
581                         getTestDeviceOptions().getSerivceAccountJsonKeyFile(),
582                         getRunUtil());
583         if (output == null) {
584             CLog.w("Failed to collect the instance serial logs.");
585             return;
586         }
587         try (ByteArrayInputStreamSource source =
588                 new ByteArrayInputStreamSource(output.getBytes())) {
589             logger.testLog("gce_full_serial_log", LogDataType.TEXT, source);
590         }
591     }
592 
593     /** Log the information related to the stable host image used. */
logStableHostImageInfos(IBuildInfo build)594     public void logStableHostImageInfos(IBuildInfo build) {
595         AcloudConfigParser config = AcloudConfigParser.parseConfig(getAvdConfigFile());
596         if (config == null) {
597             CLog.e("Failed to parse our acloud config.");
598             return;
599         }
600         if (build == null) {
601             return;
602         }
603         if (config.getValueForKey(AcloudKeys.STABLE_HOST_IMAGE_NAME) != null) {
604             build.addBuildAttribute(
605                     AcloudKeys.STABLE_HOST_IMAGE_NAME.toString(),
606                     config.getValueForKey(AcloudKeys.STABLE_HOST_IMAGE_NAME));
607         }
608         if (config.getValueForKey(AcloudKeys.STABLE_HOST_IMAGE_PROJECT) != null) {
609             build.addBuildAttribute(
610                     AcloudKeys.STABLE_HOST_IMAGE_PROJECT.toString(),
611                     config.getValueForKey(AcloudKeys.STABLE_HOST_IMAGE_PROJECT));
612         }
613     }
614 
615     /**
616      * Returns the {@link TestDeviceOptions} associated with the device that the gce manager was
617      * initialized with.
618      */
getTestDeviceOptions()619     private TestDeviceOptions getTestDeviceOptions() {
620         return mDeviceOptions;
621     }
622 
623     @VisibleForTesting
getAvdConfigFile()624     File getAvdConfigFile() {
625         if (getTestDeviceOptions().getAvdConfigTestResourceName() != null) {
626             return BuildInfo.getTestResource(
627                     mTestResourceBuildInfos, getTestDeviceOptions().getAvdConfigTestResourceName());
628         }
629         return getTestDeviceOptions().getAvdConfigFile();
630     }
631 }
632