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