package hudson.plugins.selenium;
import com.thoughtworks.selenium.grid.hub.Environment;
import com.thoughtworks.selenium.grid.hub.NoSuchSessionException;
import com.thoughtworks.selenium.grid.hub.remotecontrol.DynamicRemoteControlPool;
import com.thoughtworks.selenium.grid.hub.remotecontrol.RemoteControlProxy;
import com.thoughtworks.selenium.grid.hub.remotecontrol.RemoteControlSession;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Set;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.util.logging.Logger;
import java.util.logging.Level;
/**
* {@link DynamicRemoteControlPool} that uses labels for matching.
*
* <P>
* The "environment" that Selenium see is '/'-separated list of labels for the slave
* (like /a/b/c/d), and we match incoming label to determine the {@link RemoteControlProxy}.
*
* @author Kohsuke Kawaguchi
*/
public class HudsonRemoteControlPool implements DynamicRemoteControlPool {
private final Set<RemoteControlProxy> all = new HashSet<RemoteControlProxy>();
private final Map<String,RemoteControlSession> sessions = new HashMap<String, RemoteControlSession>();
public synchronized void register(RemoteControlProxy rc) {
all.add(rc);
}
public synchronized boolean unregister(RemoteControlProxy rc) {
return all.remove(rc);
}
public boolean isRegistered(RemoteControlProxy proxy) {
return all.contains(proxy);
}
public List<RemoteControlProxy> allRegisteredRemoteControls() {
return new ArrayList<RemoteControlProxy>(all);
}
public synchronized List<RemoteControlProxy> availableRemoteControls() {
List<RemoteControlProxy> r = new ArrayList<RemoteControlProxy>(all.size());
for (RemoteControlProxy rc : all)
if(rc.canHandleNewSession())
r.add(rc);
return r;
}
public synchronized List<RemoteControlProxy> reservedRemoteControls() {
List<RemoteControlProxy> r = new ArrayList<RemoteControlProxy>(all.size());
for (RemoteControlProxy rc : all)
if(rc.sessionInProgress())
r.add(rc);
return r;
}
public synchronized void unregisterAllUnresponsiveRemoteControls() {
for (RemoteControlProxy rc : all.toArray(new RemoteControlProxy[0])) {
if (rc.unreliable()) {
LOGGER.log(Level.WARNING, "Unregistering unreliable RC " + rc);
unregister(rc);
}
}
}
public synchronized void recycleAllSessionsIdleForTooLong(double maxIdleTimeInSeconds) {
final int maxIdleTimeInMilliseconds = (int) (maxIdleTimeInSeconds * 1000);
for (RemoteControlSession session : sessions.values().toArray(new RemoteControlSession[0])) {
if (session.innactiveForMoreThan(maxIdleTimeInMilliseconds))
releaseForSession(session.sessionId());
}
}
public synchronized RemoteControlProxy reserve(Environment env) {
String[] keys = env.name().split("&");
for (int i = 0; i < keys.length; i++)
keys[i] = '/'+keys[i]+'/';
while(true) {
boolean hadMatch=false;
for (RemoteControlProxy rc : all) {
boolean doesMatch = matches(rc,keys);
hadMatch |= doesMatch;
if(doesMatch && rc.canHandleNewSession()) {
rc.registerNewSession();
return rc;
}
}
// is there any point in waiting?
if(!hadMatch) {
if(all.isEmpty())
throw new IllegalArgumentException("No RCs available");
else
throw new IllegalArgumentException("No RC satisfies the label criteria: "+env.name()+" - "+all);
}
try {
wait();
} catch (InterruptedException e) {
// this is totally broken IMO, but the reserve method doesn't allow us to return
LOGGER.log(Level.WARNING, "Interrupted while reserving remote control for "+env.name(), e);
}
}
}
private boolean matches(RemoteControlProxy rc, String[] keys) {
for (String key : keys)
if(!rc.environment().contains(key))
return false;
return true;
}
public synchronized void release(RemoteControlProxy rc) {
rc.unregisterSession();
notifyAll();
}
public synchronized void associateWithSession(RemoteControlProxy rc, String sessionId) {
RemoteControlSession old = sessions.put(sessionId, new RemoteControlSession(sessionId,rc));
if(old!=null)
throw new IllegalStateException("Session ID "+sessionId+" is already used by "+old);
}
public synchronized RemoteControlProxy retrieve(String sessionId) {
RemoteControlSession session = sessions.get(sessionId);
if (session==null) throw new NoSuchSessionException(sessionId);
return session.remoteControl();
}
public synchronized void releaseForSession(String sessionId) {
RemoteControlProxy rc = retrieve(sessionId);
sessions.remove(sessionId);
rc.terminateSession(sessionId);
rc.unregisterSession();
notifyAll();
}
public synchronized void updateSessionLastActiveAt(String sessionId) {
sessions.get(sessionId).updateLastActiveAt();
}
private static final Logger LOGGER = Logger.getLogger(HudsonRemoteControlPool.class.getName());
}