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