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.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.StubDevice; 24 import com.android.tradefed.device.TestDevice; 25 import com.android.tradefed.device.cloud.GceAvdInfo.GceStatus; 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.ITestLoggerReceiver; 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.FileUtil; 34 import com.android.tradefed.util.StreamUtil; 35 36 import com.google.common.annotations.VisibleForTesting; 37 38 import java.io.File; 39 import java.io.IOException; 40 import java.util.List; 41 42 /** 43 * A device running inside a virtual machine that we manage remotely via a Tradefed instance inside 44 * the VM. 45 */ 46 public class ManagedRemoteDevice extends TestDevice implements ITestLoggerReceiver { 47 48 private GceManager mGceHandler = null; 49 private GceAvdInfo mGceAvd; 50 private ITestLogger mTestLogger; 51 52 /** 53 * Creates a {@link ManagedRemoteDevice}. 54 * 55 * @param device the associated {@link IDevice} 56 * @param stateMonitor the {@link IDeviceStateMonitor} mechanism to use 57 * @param allocationMonitor the {@link IDeviceMonitor} to inform of allocation state changes. 58 */ ManagedRemoteDevice( IDevice device, IDeviceStateMonitor stateMonitor, IDeviceMonitor allocationMonitor)59 public ManagedRemoteDevice( 60 IDevice device, IDeviceStateMonitor stateMonitor, IDeviceMonitor allocationMonitor) { 61 super(device, stateMonitor, allocationMonitor); 62 } 63 64 @Override preInvocationSetup(IBuildInfo info, List<IBuildInfo> testResourceBuildInfos)65 public void preInvocationSetup(IBuildInfo info, List<IBuildInfo> testResourceBuildInfos) 66 throws TargetSetupError, DeviceNotAvailableException { 67 super.preInvocationSetup(info, testResourceBuildInfos); 68 mGceAvd = null; 69 70 // We create a brand new GceManager each time to ensure clean state. 71 mGceHandler = 72 new GceManager(getDeviceDescriptor(), getOptions(), info, testResourceBuildInfos); 73 getGceHandler().logStableHostImageInfos(info); 74 setFastbootEnabled(false); 75 76 // Launch GCE helper script. 77 long startTime = getCurrentTime(); 78 launchGce(); 79 long remainingTime = getOptions().getGceCmdTimeout() - (getCurrentTime() - startTime); 80 if (remainingTime < 0) { 81 throw new DeviceNotAvailableException( 82 String.format( 83 "Failed to launch GCE after %sms", getOptions().getGceCmdTimeout()), 84 getSerialNumber()); 85 } 86 } 87 88 /** {@inheritDoc} */ 89 @Override postInvocationTearDown()90 public void postInvocationTearDown() { 91 try { 92 CLog.i("Shutting down GCE device %s", getSerialNumber()); 93 // Log the last part of the logcat from the tear down. 94 if (!(getIDevice() instanceof StubDevice)) { 95 try (InputStreamSource logcatSource = getLogcat()) { 96 clearLogcat(); 97 String name = "device_logcat_teardown_gce"; 98 mTestLogger.testLog(name, LogDataType.LOGCAT, logcatSource); 99 } 100 } 101 102 if (mGceAvd != null) { 103 // attempt to get a bugreport if Gce Avd is a failure 104 if (!GceStatus.SUCCESS.equals(mGceAvd.getStatus())) { 105 // Get a bugreport via ssh 106 getSshBugreport(); 107 } 108 // Log the serial output of the instance. 109 getGceHandler().logSerialOutput(mGceAvd, mTestLogger); 110 111 // Fetch remote files 112 CommonLogRemoteFileUtil.fetchCommonFiles( 113 mTestLogger, mGceAvd, getOptions(), getRunUtil()); 114 115 // Cleanup GCE first to make sure ssh tunnel has nowhere to go. 116 if (!getOptions().shouldSkipTearDown()) { 117 getGceHandler().shutdownGce(); 118 } 119 } 120 121 setFastbootEnabled(false); 122 123 if (getGceHandler() != null) { 124 getGceHandler().cleanUp(); 125 } 126 } finally { 127 // Ensure parent postInvocationTearDown is always called. 128 super.postInvocationTearDown(); 129 } 130 } 131 132 @Override setTestLogger(ITestLogger testLogger)133 public void setTestLogger(ITestLogger testLogger) { 134 mTestLogger = testLogger; 135 } 136 137 /** Returns the {@link GceAvdInfo} describing the remote instance. */ getRemoteAvdInfo()138 public GceAvdInfo getRemoteAvdInfo() { 139 return mGceAvd; 140 } 141 142 /** Launch the actual gce device based on the build info. */ launchGce()143 protected void launchGce() throws TargetSetupError { 144 TargetSetupError exception = null; 145 for (int attempt = 0; attempt < getOptions().getGceMaxAttempt(); attempt++) { 146 try { 147 mGceAvd = getGceHandler().startGce(); 148 if (mGceAvd != null) break; 149 } catch (TargetSetupError tse) { 150 CLog.w( 151 "Failed to start Gce with attempt: %s out of %s. With Exception: %s", 152 attempt + 1, getOptions().getGceMaxAttempt(), tse); 153 exception = tse; 154 } 155 } 156 if (mGceAvd == null) { 157 throw exception; 158 } else { 159 CLog.i("GCE AVD has been started: %s", mGceAvd); 160 if (GceAvdInfo.GceStatus.BOOT_FAIL.equals(mGceAvd.getStatus())) { 161 throw new TargetSetupError(mGceAvd.getErrors(), getDeviceDescriptor()); 162 } 163 } 164 } 165 166 /** Capture a remote bugreport by ssh-ing into the device directly. */ getSshBugreport()167 private void getSshBugreport() { 168 File bugreportFile = null; 169 try { 170 bugreportFile = 171 GceManager.getNestedDeviceSshBugreportz(mGceAvd, getOptions(), getRunUtil()); 172 if (bugreportFile != null) { 173 InputStreamSource bugreport = new FileInputStreamSource(bugreportFile); 174 mTestLogger.testLog("bugreportz-ssh", LogDataType.BUGREPORTZ, bugreport); 175 StreamUtil.cancel(bugreport); 176 } 177 } catch (IOException e) { 178 CLog.e(e); 179 } finally { 180 FileUtil.deleteFile(bugreportFile); 181 } 182 } 183 184 /** Returns the current system time. Exposed for testing. */ 185 @VisibleForTesting getCurrentTime()186 protected long getCurrentTime() { 187 return System.currentTimeMillis(); 188 } 189 190 /** Returns the instance of the {@link GceManager}. */ 191 @VisibleForTesting getGceHandler()192 GceManager getGceHandler() { 193 return mGceHandler; 194 } 195 } 196