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.recovery; 17 18 import com.android.annotations.VisibleForTesting; 19 import com.android.tradefed.command.ICommandScheduler; 20 import com.android.tradefed.command.ICommandScheduler.IScheduledInvocationListener; 21 import com.android.tradefed.config.ConfigurationException; 22 import com.android.tradefed.config.GlobalConfiguration; 23 import com.android.tradefed.config.Option; 24 import com.android.tradefed.device.DeviceAllocationState; 25 import com.android.tradefed.device.DeviceManager.FastbootDevice; 26 import com.android.tradefed.device.FreeDeviceState; 27 import com.android.tradefed.device.IDeviceManager; 28 import com.android.tradefed.device.IManagedTestDevice; 29 import com.android.tradefed.device.IMultiDeviceRecovery; 30 import com.android.tradefed.device.ITestDevice; 31 import com.android.tradefed.device.StubDevice; 32 import com.android.tradefed.invoker.IInvocationContext; 33 import com.android.tradefed.log.LogUtil.CLog; 34 import com.android.tradefed.util.QuotationAwareTokenizer; 35 36 import java.util.ArrayList; 37 import java.util.Arrays; 38 import java.util.List; 39 import java.util.Map; 40 41 /** 42 * Generic base {@link IMultiDeviceRecovery} to run a tradefed configuration to do the recovery 43 * step. 44 */ 45 public class RunConfigDeviceRecovery implements IMultiDeviceRecovery { 46 47 @Option(name = "disable", description = "Completely disable the recoverer.") 48 private boolean mDisable = false; 49 50 @Option( 51 name = "recovery-config-name", 52 description = "The configuration to be used on the device to recover.", 53 mandatory = true 54 ) 55 private String mRecoveryConfigName = null; 56 57 @Option( 58 name = "extra-arg", 59 description = "Extra arguments to be passed to the recovery " + "invocation." 60 ) 61 private List<String> mExtraArgs = new ArrayList<>(); 62 63 @Override recoverDevices(List<IManagedTestDevice> managedDevices)64 public void recoverDevices(List<IManagedTestDevice> managedDevices) { 65 if (mDisable) { 66 return; 67 } 68 69 for (IManagedTestDevice device : managedDevices) { 70 if (DeviceAllocationState.Allocated.equals(device.getAllocationState())) { 71 continue; 72 } 73 if (device.getIDevice() instanceof StubDevice 74 && !(device.getIDevice() instanceof FastbootDevice)) { 75 continue; 76 } 77 if (shouldSkip(device)) { 78 continue; 79 } 80 81 List<String> argList = new ArrayList<>(); 82 argList.add(mRecoveryConfigName); 83 84 List<String> deviceExtraArgs = getExtraArguments(device); 85 if (deviceExtraArgs == null) { 86 CLog.w("Something went wrong recovery cannot be attempted."); 87 continue; 88 } 89 90 argList.addAll(deviceExtraArgs); 91 for (String args : mExtraArgs) { 92 String[] extraArgs = QuotationAwareTokenizer.tokenizeLine(args); 93 if (extraArgs.length != 0) { 94 argList.addAll(Arrays.asList(extraArgs)); 95 } 96 } 97 98 String serial = device.getSerialNumber(); 99 ITestDevice deviceToRecover = getDeviceManager().forceAllocateDevice(serial); 100 if (deviceToRecover == null) { 101 CLog.e("Fail to force allocate '%s'", serial); 102 continue; 103 } 104 try { 105 getCommandScheduler() 106 .execCommand( 107 new FreeDeviceHandler(getDeviceManager()), 108 deviceToRecover, 109 argList.toArray(new String[0])); 110 } catch (ConfigurationException e) { 111 CLog.e("Device multi recovery is misconfigured"); 112 CLog.e(e); 113 // In this case, the device doesn't go through regular de-allocation so we 114 // explicitly deallocate. 115 getDeviceManager().freeDevice(device, FreeDeviceState.UNAVAILABLE); 116 return; 117 } 118 } 119 } 120 121 /** 122 * Get the list of extra arguments to be passed to the configuration. If null is returned 123 * something went wrong and recovery should be attempted. 124 * 125 * @param device The {@link ITestDevice} to run recovery against 126 * @return The list of extra arguments to be used. Or null if something went wrong. 127 */ getExtraArguments(ITestDevice device)128 public List<String> getExtraArguments(ITestDevice device) { 129 return new ArrayList<>(); 130 } 131 132 /** 133 * Extra chance to skip the recovery on a given device by doing extra checks. 134 * 135 * @param device The {@link IManagedTestDevice} considered for recovery. 136 * @return True if recovery should be skipped. 137 */ shouldSkip(IManagedTestDevice device)138 public boolean shouldSkip(IManagedTestDevice device) { 139 return false; 140 } 141 142 /** Returns a {@link IDeviceManager} instance. Exposed for testing. */ 143 @VisibleForTesting getDeviceManager()144 protected IDeviceManager getDeviceManager() { 145 return GlobalConfiguration.getInstance().getDeviceManager(); 146 } 147 148 /** Returns a {@link ICommandScheduler} instance. Exposed for testing. */ 149 @VisibleForTesting getCommandScheduler()150 protected ICommandScheduler getCommandScheduler() { 151 return GlobalConfiguration.getInstance().getCommandScheduler(); 152 } 153 154 /** Handler to free up the device once the invocation completes */ 155 private class FreeDeviceHandler implements IScheduledInvocationListener { 156 157 private final IDeviceManager mDeviceManager; 158 FreeDeviceHandler(IDeviceManager deviceManager)159 FreeDeviceHandler(IDeviceManager deviceManager) { 160 mDeviceManager = deviceManager; 161 } 162 163 @Override invocationComplete( IInvocationContext context, Map<ITestDevice, FreeDeviceState> devicesStates)164 public void invocationComplete( 165 IInvocationContext context, Map<ITestDevice, FreeDeviceState> devicesStates) { 166 for (ITestDevice device : context.getDevices()) { 167 mDeviceManager.freeDevice(device, devicesStates.get(device)); 168 if (device instanceof IManagedTestDevice) { 169 // This is quite an important setting so we do make sure it's reset. 170 ((IManagedTestDevice) device).setFastbootPath(mDeviceManager.getFastbootPath()); 171 } 172 } 173 } 174 } 175 } 176