org.openqa.grid.internal.TestSlot Maven / Gradle / Ivy
Show all versions of selenium-server Show documentation
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.openqa.grid.internal;
import com.google.common.base.Throwables;
import org.openqa.grid.common.SeleniumProtocol;
import org.openqa.grid.common.exception.GridException;
import org.openqa.grid.internal.listeners.TestSessionListener;
import org.openqa.grid.internal.utils.CapabilityMatcher;
import org.openqa.grid.internal.utils.configuration.GridHubConfiguration;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.InvalidParameterException;
import java.time.Clock;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;
/**
* The entity on a proxy that can host a test session. A test slot has only 1 desired capabilities (
* firefox or chrome for instance, but if a remoteproxy needs to support both, the remoteproxy will
* need 2 TestSlots ) A TestSlot can host 1 TestSession max at a time.
*
* The listener ({@link TestSessionListener} attached to the test session of this test slot is
* thread safe. If 2 threads are trying to execute the before / after session, only 1 will be
* executed.The other one will be discarded.
*
* This class sees multiple threads but is currently sort-of protected by the lock in GridRegistry.
* Unfortunately the CleanUpThread also messes around in here, so it should be thread safe on its
* own.
*
*/
public class TestSlot {
private static final Logger log = Logger.getLogger(TestSlot.class.getName());
private final Map capabilities;
private final RemoteProxy proxy;
private final SeleniumProtocol protocol;
private final String path;
private final CapabilityMatcher matcher;
private final Lock lock = new ReentrantLock();
private volatile TestSession currentSession;
private volatile boolean beingReleased = false;
private boolean showWarning = false;
private long lastSessionStart = -1;
/**
* Create a new test slot specifying a custom protocol path
* @param proxy the {@link RemoteProxy} which includes this test slot
* @param protocol the {@link SeleniumProtocol} this test slot conforms to
* @param path the protocol path this test slot uses
* @param capabilities capabilities of this test slot
*/
public TestSlot(
RemoteProxy proxy,
SeleniumProtocol protocol,
String path,
Map capabilities) {
this.proxy = proxy;
this.protocol = protocol;
this.path = path;
CapabilityMatcher c = proxy.getCapabilityHelper();
if (c == null) {
throw new InvalidParameterException("the proxy needs to have a valid "
+ "capabilityMatcher to support have some test slots attached to it");
}
this.matcher = proxy.getCapabilityHelper();
this.capabilities = capabilities;
}
/**
* Create a new test slot
* @param proxy the {@link RemoteProxy} which includes this test slot
* @param protocol the {@link SeleniumProtocol} this test slot conforms to
* @param capabilities capabilities of this test slot
*/
public TestSlot(RemoteProxy proxy, SeleniumProtocol protocol, Map capabilities) {
this(proxy, protocol, protocol.getPathConsideringCapabilitiesMap(capabilities), capabilities);
}
/**
* @return the capabilities of this test slot
*/
public Map getCapabilities() {
return Collections.unmodifiableMap(capabilities);
}
/**
* @return the {@link RemoteProxy} that hosts this slot.
*/
public RemoteProxy getProxy() {
return proxy;
}
/**
* Try to get a new session for the test slot for the desired capability. To define if the
* test slot can host the desired capabilities, {@link CapabilityMatcher#matches(Map, Map)} is
* invoked.
*
* Use {@link GridHubConfiguration#capabilityMatcher} on the proxy hosting the test slot to
* modify the definition of match
*
* @param desiredCapabilities capabilities for the new session
* @return a new session linked to that testSlot if possible, null otherwise.
*/
public TestSession getNewSession(Map desiredCapabilities) {
try {
lock.lock();
if (currentSession != null) {
return null;
}
if (matches(desiredCapabilities)) {
log.info("Trying to create a new session on test slot " + this.capabilities);
TestSession session = new TestSession(this, desiredCapabilities, Clock.systemUTC());
currentSession = session;
lastSessionStart = System.currentTimeMillis();
return session;
}
return null;
} finally {
lock.unlock();
}
}
/**
* the type of protocol for the TestSlot.Ideally should always be webdriver, but can also be
* selenium1 protocol for backward compatibility purposes.
*
* @return the protocol for this TestSlot
*/
public SeleniumProtocol getProtocol() {
return protocol;
}
/**
* the path the server is using to handle the request. Typically /wd/hub for a webdriver based
* protocol and /selenium-server/driver/ for a selenium1 based protocol
*
* @return the path the server is using for the requests of this slot.
*/
public String getPath() {
return path;
}
/**
* @param desiredCapabilities capabilities for the new session
* @return true if the desired capabilities matches for the
* {@link RemoteProxy#getCapabilityHelper()}
*/
public boolean matches(Map desiredCapabilities) {
return matcher.matches(capabilities, desiredCapabilities);
}
/**
* get the test session currently executed on this test slot.
*
* @return the session. Null if the slot is not used at the moment.
*/
public TestSession getSession() {
return currentSession;
}
/**
* Starts the release process for the TestSlot. Once the release process has started, the clients
* can't access the test slot any more, but the slot can't be reserved for another test until
* finishReleaseProcess is called.
*
* That gives time to run exactly once the cleanup operation needed using @see
* {@link TestSessionListener#afterSession(TestSession)}
*
* @return true if that's the first thread trying to release this test slot, false otherwise.
* @see TestSlot#finishReleaseProcess()
*/
public boolean startReleaseProcess() {
if (currentSession == null) {
return false;
}
try {
lock.lock();
if (beingReleased) {
return false;
}
beingReleased = true;
return true;
} finally {
lock.unlock();
}
}
/**
* releasing all the resources. The slot can now be reused.
*/
public void finishReleaseProcess() {
try {
lock.lock();
doFinishRelease();
} finally {
lock.unlock();
}
}
/**
* Finish releasing all resources so the slot can be reused.
*/
public void doFinishRelease() {
currentSession = null;
beingReleased = false;
}
/**
* @return the test session internal key
*/
public String getInternalKey() {
return currentSession == null ? null : currentSession.getInternalKey();
}
/**
* @return invokes after session {@link TestSessionListener} events on this test slot
*/
public boolean performAfterSessionEvent() {
// run the pre-release listener
try {
if (proxy instanceof TestSessionListener) {
if (showWarning && proxy.getMaxNumberOfConcurrentTestSessions() != 1) {
log.warning("WARNING : using a afterSession on a proxy that can support multiple tests is risky.");
showWarning = false;
}
((TestSessionListener) proxy).afterSession(currentSession);
}
} catch (Throwable t) {
log.severe(String.format(
"Error running afterSession for %s, the test slot is now dead: %s\n%s", currentSession,
t.getMessage(), Throwables.getStackTraceAsString(t)));
return false;
}
return true;
}
@Override
public String toString() {
return currentSession == null ? "no session" : currentSession.toString();
}
/**
* get the full URL the underlying server is listening on for selenium / webdriver commands.
*
* @return the url
*/
public URL getRemoteURL() {
String u = getProxy().getRemoteHost() + getPath();
try {
return new URL(u);
} catch (MalformedURLException e) {
throw new GridException("Configuration error for the node." + u + " isn't a valid URL");
}
}
/**
* @return System.currentTimeMillis() of when the session was started, otherwise -1
*/
public long getLastSessionStart() {
return lastSessionStart;
}
}