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