blob: 53456dc70c7b7491d43544fc7ce2f4893e989766 [file] [log] [blame]
Brett Chabot33513a52011-11-09 18:20:04 -08001/*
2 * Copyright (C) 2011 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 */
16package com.android.tradefed.command;
17
18import com.android.ddmlib.Log;
19import com.android.ddmlib.Log.LogLevel;
20import com.android.tradefed.device.IDeviceManager;
21import com.android.tradefed.device.IDeviceManager.FreeDeviceState;
22import com.android.tradefed.device.ITestDevice;
23import com.android.tradefed.log.LogUtil.CLog;
24import com.android.tradefed.util.ArrayUtil;
25
26import java.io.BufferedReader;
27import java.io.IOException;
28import java.io.InputStreamReader;
29import java.io.PrintWriter;
30import java.net.ServerSocket;
31import java.net.Socket;
32import java.util.Arrays;
33import java.util.Hashtable;
34import java.util.Map;
35
36/**
37 * Class that receives remote commands to add and remove devices from use via a socket.
38 * <p/>
39 * Currently accepts only one remote connection at one time, and processes incoming commands
40 * serially.
41 * <p/>
42 * Usage:
43 * <pre>
44 * RemoteManager r = new RemoteManager(deviceMgr, scheduler);
45 * r.start();
46 * int port = r.getPort();
47 * ... inform client of port to use. Shuts down when instructed by client or on #cancel()
48 * </pre>
49 */
50public class RemoteManager extends Thread {
51
52 // constants that define wire protocol between RemoteClient and RemoteManager
53 static final String DELIM = ";";
54 static final String FILTER = "filter";
55 static final String UNFILTER = "unfilter";
56 static final String ALL_DEVICES = "*";
57 static final String CLOSE = "close";
58 static final String ADD_COMMAND = "add_command";
59
60 private ServerSocket mServerSocket = null;
61 private boolean mCancel = false;
62 private final IDeviceManager mDeviceManager;
63 private final ICommandScheduler mScheduler;
64 private Map<String, ITestDevice> mFilteredDeviceMap = new Hashtable<String, ITestDevice>();
65
66 /**
67 * Creates a {@link RemoteManager}.
68 *
69 * @param manager the {@link IDeviceManager} to use to allocate and free devices.
70 * @param scheduler the {@link ICommandScheduler} to use to schedule commands.
71 */
72 public RemoteManager(IDeviceManager manager, ICommandScheduler scheduler) {
73 mDeviceManager = manager;
74 mScheduler = scheduler;
75 }
76
77 /**
78 * The main thread body of the remote manager.
79 * <p/>
80 * Creates a server socket, and waits for client connections.
81 */
82 @Override
83 public void run() {
84 synchronized (this) {
85 try {
86 mServerSocket = new ServerSocket(0);
87 } catch (IOException e) {
88 CLog.e("Failed to open server socket: %s", e);
89 return;
90 } finally {
91 // notify any listeners that the socket has been created
92 notifyAll();
93 }
94 }
95 try {
96 processClientConnections(mServerSocket);
97 } finally {
98 freeAllDevices();
99 closeSocket(mServerSocket);
100 }
101 }
102
103 /**
104 * Gets the socket port the remote manager is listening on, blocking for a short time if
105 * necessary.
106 * <p/>
107 * {@link #start()} should be called before this method.
108 * @return
109 */
110 public synchronized int getPort() {
111 if (mServerSocket == null) {
112 try {
113 wait(10*1000);
114 } catch (InterruptedException e) {
115 // ignore
116 }
117 }
118 if (mServerSocket == null) {
119 return -1;
120 }
121 return mServerSocket.getLocalPort();
122 }
123
124 private void processClientConnections(ServerSocket serverSocket) {
125 while (!mCancel) {
126 Socket clientSocket = null;
127 BufferedReader in = null;
128 PrintWriter out = null;
129 try {
130 clientSocket = serverSocket.accept();
131 in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
132 out = new PrintWriter(clientSocket.getOutputStream(), true);
133 processClientCommands(in, out);
134 } catch (IOException e) {
135 CLog.e("Failed to accept connection: %s", e);
136 } finally {
137 closeReader(in);
138 closeWriter(out);
139 closeSocket(clientSocket);
140 }
141 }
142 }
143
144 private void processClientCommands(BufferedReader in, PrintWriter out) throws IOException {
145 String line = null;
146 while ((line = in.readLine()) != null && !mCancel) {
147 boolean result = false;
148 String[] commandSegments = line.split(DELIM);
149 String cmdType = commandSegments[0];
150 if (FILTER.equals(cmdType)) {
151 result = processFilterCommand(commandSegments);
152 } else if (UNFILTER.equals(cmdType)) {
153 result = processUnfilterCommand(commandSegments);
154 } else if (CLOSE.equals(cmdType)) {
155 cancel();
156 result = true;
157 } else if (ADD_COMMAND.equals(cmdType)) {
158 result = processAddCommand(commandSegments);
159 }
160 sendAck(result, out);
161 }
162 }
163
164 private boolean processFilterCommand(final String[] commandSegments) {
165 if (commandSegments.length < 2) {
166 CLog.e("Invalid command received: %s", ArrayUtil.join(" ", (Object[])commandSegments));
167 return false;
168 }
169 final String serial = commandSegments[1];
170 ITestDevice allocatedDevice = mDeviceManager.forceAllocateDevice(serial);
171 if (allocatedDevice != null) {
172 Log.logAndDisplay(LogLevel.INFO, "RemoteManager",
173 String.format("Allocating device %s that is still in use by remote tradefed",
174 serial));
175 mFilteredDeviceMap.put(serial, allocatedDevice);
176 return true;
177 } else {
178 CLog.e("Failed to allocate remote device %s", serial);
179 return false;
180 }
181 }
182
183 private boolean processUnfilterCommand(final String[] commandSegments) {
184 if (commandSegments.length < 2) {
185 CLog.e("Invalid command received: %s", ArrayUtil.join(" ", (Object[])commandSegments));
186 return false;
187 }
188 // TODO: consider making this synchronous, and sending ack back to client once allocated
189 final String serial = commandSegments[1];
190 if (ALL_DEVICES.equals(serial)) {
191 freeAllDevices();
192 return true;
193 } else {
194 ITestDevice d = mFilteredDeviceMap.remove(serial);
195 if (d != null) {
196 Log.logAndDisplay(LogLevel.INFO, "RemoteManager",
197 String.format("Freeing device %s no longer in use by remote tradefed",
198 serial));
199 mDeviceManager.freeDevice(d, FreeDeviceState.AVAILABLE);
200 return true;
201 } else {
202 CLog.w("Could not find device to free %s", serial);
203 }
204 }
205 return false;
206 }
207
208 private boolean processAddCommand(final String[] commandSegments) {
209 if (commandSegments.length < 3) {
210 CLog.e("Invalid command received: %s", ArrayUtil.join(" ", (Object[])commandSegments));
211 return false;
212 }
213 long totalTime = Long.parseLong(commandSegments[1]);
214 String[] cmdArgs = Arrays.copyOfRange(commandSegments, 2, commandSegments.length);
215 Log.logAndDisplay(LogLevel.INFO, "RemoteManager",
216 String.format("Adding command '%s'", ArrayUtil.join(" ", (Object[])cmdArgs)));
217 return mScheduler.addCommand(cmdArgs, null, totalTime);
218 }
219
220 private void freeAllDevices() {
221 for (ITestDevice d : mFilteredDeviceMap.values()) {
222 Log.logAndDisplay(LogLevel.INFO, "RemoteManager",
223 String.format("Freeing device %s no longer in use by remote tradefed",
224 d.getSerialNumber()));
225
226 mDeviceManager.freeDevice(d, FreeDeviceState.AVAILABLE);
227 }
228 mFilteredDeviceMap.clear();
229 }
230
231 private void sendAck(boolean result, PrintWriter out) {
232 out.println(result);
233 }
234
235 /**
236 * Cancel the remote manager.
237 */
238 public synchronized void cancel() {
239 if (!mCancel) {
240 mCancel = true;
241 Log.logAndDisplay(LogLevel.INFO, "RemoteManager", "Closing remote manager");
242 }
243 }
244
245 private void closeSocket(ServerSocket serverSocket) {
246 if (serverSocket != null) {
247 try {
248 serverSocket.close();
249 } catch (IOException e) {
250 // ignore
251 }
252 }
253 }
254
255 private void closeSocket(Socket clientSocket) {
256 if (clientSocket != null) {
257 try {
258 clientSocket.close();
259 } catch (IOException e) {
260 // ignore
261 e.printStackTrace();
262 }
263 }
264 }
265
266 private void closeReader(BufferedReader in) {
267 if (in != null) {
268 try {
269 in.close();
270 } catch (IOException e) {
271 // ignore
272 }
273 }
274 }
275
276 private void closeWriter(PrintWriter out) {
277 if (out != null) {
278 out.close();
279 }
280 }
281
282 /**
283 * @return <code>true</code> if a cancel has been requested
284 */
285 public boolean isCanceled() {
286 return mCancel;
287 }
288}