1 /* 2 * Copyright (C) 2019 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.ddmlib.IDevice; 19 import com.android.tradefed.build.IBuildInfo; 20 import com.android.tradefed.device.DeviceNotAvailableException; 21 import com.android.tradefed.device.IDeviceMonitor; 22 import com.android.tradefed.device.IDeviceStateMonitor; 23 import com.android.tradefed.device.TestDevice; 24 import com.android.tradefed.device.cloud.CommonLogRemoteFileUtil.KnownLogFileEntry; 25 import com.android.tradefed.invoker.RemoteInvocationExecution; 26 import com.android.tradefed.log.ITestLogger; 27 import com.android.tradefed.log.LogUtil.CLog; 28 import com.android.tradefed.result.FileInputStreamSource; 29 import com.android.tradefed.result.InputStreamSource; 30 import com.android.tradefed.result.LogDataType; 31 import com.android.tradefed.targetprep.TargetSetupError; 32 import com.android.tradefed.util.CommandResult; 33 import com.android.tradefed.util.CommandStatus; 34 35 import com.google.common.base.Joiner; 36 37 import java.io.File; 38 import java.text.SimpleDateFormat; 39 import java.util.Date; 40 import java.util.HashMap; 41 import java.util.List; 42 import java.util.Map; 43 44 /** 45 * Representation of the device running inside a remote Cuttlefish VM. It will alter the local 46 * device {@link TestDevice} behavior in some cases to take advantage of the setup. 47 */ 48 public class NestedRemoteDevice extends TestDevice { 49 50 // TODO: Improve the way we associate nested device with their user 51 private static final Map<String, String> IP_TO_USER = new HashMap<>(); 52 53 static { 54 IP_TO_USER.put("127.0.0.1:6520", "vsoc-01"); 55 IP_TO_USER.put("127.0.0.1:6521", "vsoc-02"); 56 IP_TO_USER.put("127.0.0.1:6522", "vsoc-03"); 57 IP_TO_USER.put("127.0.0.1:6523", "vsoc-04"); 58 IP_TO_USER.put("127.0.0.1:6524", "vsoc-05"); 59 IP_TO_USER.put("127.0.0.1:6525", "vsoc-06"); 60 IP_TO_USER.put("127.0.0.1:6526", "vsoc-07"); 61 IP_TO_USER.put("127.0.0.1:6527", "vsoc-08"); 62 IP_TO_USER.put("127.0.0.1:6528", "vsoc-09"); 63 IP_TO_USER.put("127.0.0.1:6529", "vsoc-10"); 64 IP_TO_USER.put("127.0.0.1:6530", "vsoc-11"); 65 IP_TO_USER.put("127.0.0.1:6531", "vsoc-12"); 66 IP_TO_USER.put("127.0.0.1:6532", "vsoc-13"); 67 IP_TO_USER.put("127.0.0.1:6533", "vsoc-14"); 68 IP_TO_USER.put("127.0.0.1:6534", "vsoc-15"); 69 } 70 71 /** When calling launch_cvd, the launcher.log is populated. */ 72 private static final String LAUNCHER_LOG_PATH = "/home/%s/cuttlefish_runtime/launcher.log"; 73 74 /** 75 * Creates a {@link NestedRemoteDevice}. 76 * 77 * @param device the associated {@link IDevice} 78 * @param stateMonitor the {@link IDeviceStateMonitor} mechanism to use 79 * @param allocationMonitor the {@link IDeviceMonitor} to inform of allocation state changes. 80 */ NestedRemoteDevice( IDevice device, IDeviceStateMonitor stateMonitor, IDeviceMonitor allocationMonitor)81 public NestedRemoteDevice( 82 IDevice device, IDeviceStateMonitor stateMonitor, IDeviceMonitor allocationMonitor) { 83 super(device, stateMonitor, allocationMonitor); 84 // TODO: Use IDevice directly 85 if (stateMonitor instanceof NestedDeviceStateMonitor) { 86 ((NestedDeviceStateMonitor) stateMonitor).setDevice(this); 87 } 88 } 89 90 /** Teardown and restore the virtual device so testing can proceed. */ resetVirtualDevice( ITestLogger logger, IBuildInfo info, boolean resetDueToFailure)91 public final boolean resetVirtualDevice( 92 ITestLogger logger, IBuildInfo info, boolean resetDueToFailure) 93 throws DeviceNotAvailableException { 94 String username = IP_TO_USER.get(getSerialNumber()); 95 // stop_cvd 96 String stopCvdCommand = String.format("sudo runuser -l %s -c 'stop_cvd'", username); 97 CommandResult stopCvdRes = getRunUtil().runTimedCmd(60000L, stopCvdCommand.split(" ")); 98 if (!CommandStatus.SUCCESS.equals(stopCvdRes.getStatus())) { 99 CLog.e("%s", stopCvdRes.getStderr()); 100 // Log 'adb devices' to confirm device is gone 101 CommandResult printAdbDevices = getRunUtil().runTimedCmd(60000L, "adb", "devices"); 102 CLog.e("%s\n%s", printAdbDevices.getStdout(), printAdbDevices.getStderr()); 103 // Proceed here, device could have been already gone. 104 } 105 // Synchronize this so multiple reset do not occur at the same time inside one VM. 106 synchronized (NestedRemoteDevice.class) { 107 if (resetDueToFailure) { 108 // Log the common files before restarting otherwise they are lost 109 logDebugFiles(logger, username); 110 } 111 // Restart the device without re-creating the data partitions. 112 List<String> createCommand = 113 LaunchCvdHelper.createSimpleDeviceCommand(username, true, false, false); 114 CommandResult createRes = 115 getRunUtil() 116 .runTimedCmd( 117 RemoteInvocationExecution.LAUNCH_EXTRA_DEVICE, 118 "sh", 119 "-c", 120 Joiner.on(" ").join(createCommand)); 121 if (!CommandStatus.SUCCESS.equals(createRes.getStatus())) { 122 CLog.e("%s", createRes.getStderr()); 123 captureLauncherLog(username, logger); 124 return false; 125 } 126 // Wait for the device to start for real. 127 getRunUtil().sleep(5000); 128 waitForDeviceAvailable(); 129 // Re-init the freshly started device. 130 return reInitDevice(info); 131 } 132 } 133 134 /** 135 * Log the runtime files of the virtual device before resetting it since they will be deleted. 136 */ logDebugFiles(ITestLogger logger, String username)137 private void logDebugFiles(ITestLogger logger, String username) { 138 List<KnownLogFileEntry> toFetch = 139 CommonLogRemoteFileUtil.KNOWN_FILES_TO_FETCH.get(getOptions().getInstanceType()); 140 if (toFetch != null) { 141 SimpleDateFormat formatter = new SimpleDateFormat("HH:mm:ss"); 142 for (KnownLogFileEntry entry : toFetch) { 143 File toLog = new File(String.format(entry.path, username)); 144 if (!toLog.exists()) { 145 continue; 146 } 147 try (FileInputStreamSource source = new FileInputStreamSource(toLog)) { 148 logger.testLog( 149 String.format( 150 "before_reset_%s_%s_%s", 151 toLog.getName(), username, formatter.format(new Date())), 152 entry.type, 153 source); 154 } 155 } 156 } 157 logBugreport(String.format("before_reset_%s_bugreport", username), logger); 158 } 159 160 /** TODO: Re-run the target_preparation. */ reInitDevice(IBuildInfo info)161 private boolean reInitDevice(IBuildInfo info) throws DeviceNotAvailableException { 162 // Reset recovery since it's a new device 163 setRecoveryMode(RecoveryMode.AVAILABLE); 164 try { 165 preInvocationSetup(info); 166 } catch (TargetSetupError e) { 167 CLog.e("Failed to re-init the device %s", getSerialNumber()); 168 CLog.e(e); 169 return false; 170 } 171 // Success 172 return true; 173 } 174 175 /** Capture and log the launcher.log to debug why the device didn't start properly. */ captureLauncherLog(String username, ITestLogger logger)176 private void captureLauncherLog(String username, ITestLogger logger) { 177 String logName = String.format("launcher_log_failure_%s", username); 178 File launcherLog = new File(String.format(LAUNCHER_LOG_PATH, username)); 179 if (!launcherLog.exists()) { 180 CLog.e("%s doesn't exists, skip logging it.", launcherLog.getAbsolutePath()); 181 return; 182 } 183 try (InputStreamSource source = new FileInputStreamSource(launcherLog)) { 184 logger.testLog(logName, LogDataType.TEXT, source); 185 } 186 } 187 } 188