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