1 /* 2 * Copyright (C) 2023 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.internal; 17 18 import com.android.annotations.VisibleForTesting; 19 import com.android.tradefed.device.DeviceNotAvailableException; 20 import com.android.tradefed.device.ITestDevice; 21 import com.android.tradefed.device.NativeDevice; 22 import com.android.tradefed.device.StubDevice; 23 import com.android.tradefed.error.HarnessRuntimeException; 24 import com.android.tradefed.error.IHarnessException; 25 import com.android.tradefed.invoker.IInvocationContext; 26 import com.android.tradefed.invoker.logger.CurrentInvocation; 27 import com.android.tradefed.invoker.logger.CurrentInvocation.IsolationGrade; 28 import com.android.tradefed.invoker.logger.InvocationMetricLogger; 29 import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey; 30 import com.android.tradefed.log.LogUtil.CLog; 31 import com.android.tradefed.result.error.InfraErrorIdentifier; 32 import com.android.tradefed.service.TradefedFeatureClient; 33 import com.android.tradefed.util.SerializationUtil; 34 35 import com.proto.tradefed.feature.FeatureResponse; 36 37 import java.io.IOException; 38 import java.util.HashMap; 39 import java.util.Map; 40 import java.util.regex.Matcher; 41 import java.util.regex.Pattern; 42 43 /** 44 * Utility handling Cuttlefish snapshot. This is meant to only be used internally to the test 45 * harness. This shouldn't be called during a test. 46 */ 47 public class DeviceSnapshotHandler { 48 49 private final TradefedFeatureClient mClient; 50 private final IInvocationContext mContext; 51 DeviceSnapshotHandler()52 public DeviceSnapshotHandler() { 53 this(new TradefedFeatureClient(), CurrentInvocation.getInvocationContext()); 54 } 55 56 @VisibleForTesting DeviceSnapshotHandler(TradefedFeatureClient client, IInvocationContext context)57 DeviceSnapshotHandler(TradefedFeatureClient client, IInvocationContext context) { 58 mClient = client; 59 mContext = context; 60 } 61 62 /** 63 * Calls delete snapshot of the given device. 64 * 65 * @param device The device to delete a snapshot. Needed to get user. 66 * @param snapshotId Snapshot ID to delete. 67 * @return True if deleting snapshot was successful, false otherwise. 68 * @throws DeviceNotAvailableException 69 */ deleteSnapshot(ITestDevice device, String snapshotId)70 public void deleteSnapshot(ITestDevice device, String snapshotId) 71 throws DeviceNotAvailableException { 72 if (device.getIDevice() instanceof StubDevice) { 73 CLog.d( 74 "Device '%s' is a stub device. skipping deleting snapshot.", 75 device.getSerialNumber()); 76 return; 77 } 78 FeatureResponse response; 79 try { 80 Map<String, String> args = new HashMap<>(); 81 args.put(DeviceSnapshotFeature.SNAPSHOT_ID, snapshotId); 82 args.put(DeviceSnapshotFeature.DEVICE_NAME, mContext.getDeviceName(device)); 83 args.put(DeviceSnapshotFeature.DELETE_FLAG, "true"); 84 response = 85 mClient.triggerFeature( 86 DeviceSnapshotFeature.DEVICE_SNAPSHOT_FEATURE_NAME, args); 87 CLog.d( 88 "Response from deleting snapshot(%s) request: %s", 89 snapshotId, response.getResponse()); 90 } finally { 91 mClient.close(); 92 } 93 if (response.hasErrorInfo()) { 94 String trace = response.getErrorInfo().getErrorTrace(); 95 // Handle if it's an exception error. 96 Object o = null; 97 try { 98 o = SerializationUtil.deserialize(trace); 99 } catch (IOException | RuntimeException e) { 100 CLog.e("Failed to deserialize delete snapshot error response: %s", e.getMessage()); 101 } 102 if (o instanceof DeviceNotAvailableException) { 103 throw (DeviceNotAvailableException) o; 104 } else if (o instanceof IHarnessException) { 105 IHarnessException exception = (IHarnessException) o; 106 throw new HarnessRuntimeException("Exception while deleting snapshot.", exception); 107 } else if (o instanceof Exception) { 108 throw new HarnessRuntimeException( 109 "Exception while deleting snapshot.", 110 (Exception) o, 111 InfraErrorIdentifier.UNDETERMINED); 112 } 113 throw new HarnessRuntimeException( 114 "Exception while deleting snapshot. Unserialized error response: " + trace, 115 InfraErrorIdentifier.UNDETERMINED); 116 } 117 } 118 119 /** 120 * Calls snapshot of the given device. 121 * 122 * @param device The device to snapshot. 123 * @param snapshotId Snapshot ID for the device to be saved to. 124 * @return True if snapshot was successful, false otherwise. 125 * @throws DeviceNotAvailableException 126 */ snapshotDevice(ITestDevice device, String snapshotId)127 public void snapshotDevice(ITestDevice device, String snapshotId) 128 throws DeviceNotAvailableException { 129 if (device.getIDevice() instanceof StubDevice) { 130 CLog.d("Device '%s' is a stub device. skipping snapshot.", device.getSerialNumber()); 131 return; 132 } 133 FeatureResponse response; 134 try { 135 Map<String, String> args = new HashMap<>(); 136 args.put(DeviceSnapshotFeature.DEVICE_NAME, mContext.getDeviceName(device)); 137 args.put(DeviceSnapshotFeature.SNAPSHOT_ID, snapshotId); 138 response = 139 mClient.triggerFeature( 140 DeviceSnapshotFeature.DEVICE_SNAPSHOT_FEATURE_NAME, args); 141 CLog.d("Response from snapshot request: %s", response.getResponse()); 142 } finally { 143 mClient.close(); 144 } 145 if (response.hasErrorInfo()) { 146 String trace = response.getErrorInfo().getErrorTrace(); 147 // Handle if it's an exception error. 148 Object o = null; 149 try { 150 o = SerializationUtil.deserialize(trace); 151 } catch (IOException | RuntimeException e) { 152 CLog.e("Failed to deserialize snapshot error response: %s", e.getMessage()); 153 } 154 if (o instanceof DeviceNotAvailableException) { 155 throw (DeviceNotAvailableException) o; 156 } else if (o instanceof IHarnessException) { 157 IHarnessException exception = (IHarnessException) o; 158 throw new HarnessRuntimeException( 159 "Exception while snapshotting the device.", exception); 160 } else if (o instanceof Exception) { 161 throw new HarnessRuntimeException( 162 "Exception while snapshotting the device.", 163 (Exception) o, 164 InfraErrorIdentifier.UNDETERMINED); 165 } 166 throw new HarnessRuntimeException( 167 "Exception while snapshotting the device. Unserialized error response: " 168 + trace, 169 InfraErrorIdentifier.UNDETERMINED); 170 } 171 172 // Save snapshot performance data 173 Pattern durationPattern = Pattern.compile("Snapshot\\sfinished\\sin (\\d+)\\sms"); 174 Matcher matcher; 175 matcher = durationPattern.matcher(response.getResponse()); 176 if (matcher.find()) { 177 InvocationMetricLogger.addInvocationMetrics( 178 InvocationMetricKey.DEVICE_SNAPSHOT_SUCCESS_COUNT, 1); 179 InvocationMetricLogger.addInvocationMetrics( 180 InvocationMetricKey.DEVICE_SNAPSHOT_DURATIONS, matcher.group(1)); 181 } else { 182 InvocationMetricLogger.addInvocationMetrics( 183 InvocationMetricKey.DEVICE_SNAPSHOT_FAILURE_COUNT, 1); 184 } 185 } 186 187 /** 188 * Calls restore snapshot of the given device. 189 * 190 * @param device The device to restore. 191 * @param snapshotId Snapshot ID for the device to be restored to. 192 * @return True if restore was successful, false otherwise. 193 * @throws DeviceNotAvailableException 194 */ restoreSnapshotDevice(ITestDevice device, String snapshotId)195 public void restoreSnapshotDevice(ITestDevice device, String snapshotId) 196 throws DeviceNotAvailableException { 197 if (device.getIDevice() instanceof StubDevice) { 198 CLog.d( 199 "Device '%s' is a stub device. skipping restoring snapshot.", 200 device.getSerialNumber()); 201 return; 202 } 203 FeatureResponse response; 204 try { 205 Map<String, String> args = new HashMap<>(); 206 args.put(DeviceSnapshotFeature.SNAPSHOT_ID, snapshotId); 207 args.put(DeviceSnapshotFeature.RESTORE_FLAG, "true"); 208 args.put(DeviceSnapshotFeature.DEVICE_NAME, mContext.getDeviceName(device)); 209 response = 210 mClient.triggerFeature( 211 DeviceSnapshotFeature.DEVICE_SNAPSHOT_FEATURE_NAME, args); 212 CLog.d( 213 "Response from restoring snapshot(%s) request: %s", 214 snapshotId, response.getResponse()); 215 } finally { 216 mClient.close(); 217 } 218 if (response.hasErrorInfo()) { 219 String trace = response.getErrorInfo().getErrorTrace(); 220 // Handle if it's an exception error. 221 Object o = null; 222 try { 223 o = SerializationUtil.deserialize(trace); 224 } catch (IOException | RuntimeException e) { 225 CLog.e("Failed to deserialize snapshot error response: %s", e.getMessage()); 226 } 227 if (o instanceof DeviceNotAvailableException) { 228 throw (DeviceNotAvailableException) o; 229 } else if (o instanceof IHarnessException) { 230 IHarnessException exception = (IHarnessException) o; 231 throw new HarnessRuntimeException( 232 "Exception while restoring snapshot of the device.", exception); 233 } else if (o instanceof Exception) { 234 throw new HarnessRuntimeException( 235 "Exception while restoring snapshot of the device.", 236 (Exception) o, 237 InfraErrorIdentifier.UNDETERMINED); 238 } 239 throw new HarnessRuntimeException( 240 "Exception while restoring snapshot of the device. Unserialized error response:" 241 + " " 242 + trace, 243 InfraErrorIdentifier.UNDETERMINED); 244 } 245 if (device instanceof NativeDevice) { 246 ((NativeDevice) device).resetContentProviderSetup(); 247 } 248 CurrentInvocation.setModuleIsolation(IsolationGrade.FULLY_ISOLATED); 249 CurrentInvocation.setRunIsolation(IsolationGrade.FULLY_ISOLATED); 250 251 // Save snapshot performance data 252 Pattern durationPattern = Pattern.compile("Restoring snapshot\\sfinished\\sin (\\d+)\\sms"); 253 Matcher matcher; 254 matcher = durationPattern.matcher(response.getResponse()); 255 if (matcher.find()) { 256 InvocationMetricLogger.addInvocationMetrics( 257 InvocationMetricKey.DEVICE_SNAPSHOT_RESTORE_SUCCESS_COUNT, 1); 258 InvocationMetricLogger.addInvocationMetrics( 259 InvocationMetricKey.DEVICE_SNAPSHOT_RESTORE_DURATIONS, matcher.group(1)); 260 } else { 261 InvocationMetricLogger.addInvocationMetrics( 262 InvocationMetricKey.DEVICE_SNAPSHOT_RESTORE_FAILURE_COUNT, 1); 263 } 264 } 265 } 266