GUACAMOLE-267: Failover to other connections within same group if upstream remote desktop errors are detected.

This commit is contained in:
Michael Jumper
2017-04-16 21:41:56 -07:00
parent 8b9b4881b7
commit c9b88e2ba9

View File

@@ -39,8 +39,10 @@ import org.apache.guacamole.auth.jdbc.connection.ConnectionModel;
import org.apache.guacamole.auth.jdbc.connection.ConnectionRecordModel; import org.apache.guacamole.auth.jdbc.connection.ConnectionRecordModel;
import org.apache.guacamole.auth.jdbc.connection.ConnectionParameterModel; import org.apache.guacamole.auth.jdbc.connection.ConnectionParameterModel;
import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleResourceConflictException;
import org.apache.guacamole.GuacamoleResourceNotFoundException; import org.apache.guacamole.GuacamoleResourceNotFoundException;
import org.apache.guacamole.GuacamoleSecurityException; import org.apache.guacamole.GuacamoleSecurityException;
import org.apache.guacamole.GuacamoleUpstreamException;
import org.apache.guacamole.auth.jdbc.JDBCEnvironment; import org.apache.guacamole.auth.jdbc.JDBCEnvironment;
import org.apache.guacamole.auth.jdbc.connection.ConnectionMapper; import org.apache.guacamole.auth.jdbc.connection.ConnectionMapper;
import org.apache.guacamole.environment.Environment; import org.apache.guacamole.environment.Environment;
@@ -60,6 +62,9 @@ import org.apache.guacamole.auth.jdbc.sharingprofile.ModeledSharingProfile;
import org.apache.guacamole.auth.jdbc.sharingprofile.SharingProfileParameterMapper; import org.apache.guacamole.auth.jdbc.sharingprofile.SharingProfileParameterMapper;
import org.apache.guacamole.auth.jdbc.sharingprofile.SharingProfileParameterModel; import org.apache.guacamole.auth.jdbc.sharingprofile.SharingProfileParameterModel;
import org.apache.guacamole.auth.jdbc.user.RemoteAuthenticatedUser; import org.apache.guacamole.auth.jdbc.user.RemoteAuthenticatedUser;
import org.apache.guacamole.protocol.FailoverGuacamoleSocket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** /**
@@ -69,6 +74,11 @@ import org.apache.guacamole.auth.jdbc.user.RemoteAuthenticatedUser;
*/ */
public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelService { public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelService {
/**
* Logger for this class.
*/
private final Logger logger = LoggerFactory.getLogger(AbstractGuacamoleTunnelService.class);
/** /**
* The environment of the Guacamole server. * The environment of the Guacamole server.
*/ */
@@ -439,6 +449,10 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS
* Information describing the Guacamole client connecting to the given * Information describing the Guacamole client connecting to the given
* connection. * connection.
* *
* @param interceptErrors
* Whether errors from the upstream remote desktop should be
* intercepted and rethrown as GuacamoleUpstreamExceptions.
*
* @return * @return
* A new GuacamoleTunnel which is configured and connected to the given * A new GuacamoleTunnel which is configured and connected to the given
* connection. * connection.
@@ -448,7 +462,7 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS
* while connection configuration information is being retrieved. * while connection configuration information is being retrieved.
*/ */
private GuacamoleTunnel assignGuacamoleTunnel(ActiveConnectionRecord activeConnection, private GuacamoleTunnel assignGuacamoleTunnel(ActiveConnectionRecord activeConnection,
GuacamoleClientInformation info) throws GuacamoleException { GuacamoleClientInformation info, boolean interceptErrors) throws GuacamoleException {
// Record new active connection // Record new active connection
Runnable cleanupTask = new ConnectionCleanupTask(activeConnection); Runnable cleanupTask = new ConnectionCleanupTask(activeConnection);
@@ -488,7 +502,10 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS
getUnconfiguredGuacamoleSocket(cleanupTask), config, info); getUnconfiguredGuacamoleSocket(cleanupTask), config, info);
// Assign and return new tunnel // Assign and return new tunnel
return activeConnection.assignGuacamoleTunnel(socket, socket.getConnectionID()); if (interceptErrors)
return activeConnection.assignGuacamoleTunnel(new FailoverGuacamoleSocket(socket), socket.getConnectionID());
else
return activeConnection.assignGuacamoleTunnel(socket, socket.getConnectionID());
} }
@@ -633,7 +650,7 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS
// Connect only if the connection was successfully acquired // Connect only if the connection was successfully acquired
ActiveConnectionRecord connectionRecord = activeConnectionRecordProvider.get(); ActiveConnectionRecord connectionRecord = activeConnectionRecordProvider.get();
connectionRecord.init(user, connection); connectionRecord.init(user, connection);
return assignGuacamoleTunnel(connectionRecord, info); return assignGuacamoleTunnel(connectionRecord, info, false);
} }
@@ -653,29 +670,50 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS
if (connections.isEmpty()) if (connections.isEmpty())
throw new GuacamoleSecurityException("Permission denied."); throw new GuacamoleSecurityException("Permission denied.");
// Acquire group do {
acquire(user, connectionGroup);
// Attempt to acquire to any child // Acquire group
ModeledConnection connection; acquire(user, connectionGroup);
try {
connection = acquire(user, connections);
}
// Ensure connection group is always released if child acquire fails // Attempt to acquire to any child
catch (GuacamoleException e) { ModeledConnection connection;
release(user, connectionGroup); try {
throw e; connection = acquire(user, connections);
} }
// If session affinity is enabled, prefer this connection going forward // Ensure connection group is always released if child acquire fails
if (connectionGroup.isSessionAffinityEnabled()) catch (GuacamoleException e) {
user.preferConnection(connection.getIdentifier()); release(user, connectionGroup);
throw e;
}
// Connect to acquired child try {
ActiveConnectionRecord connectionRecord = activeConnectionRecordProvider.get();
connectionRecord.init(user, connectionGroup, connection); // Connect to acquired child
return assignGuacamoleTunnel(connectionRecord, info); ActiveConnectionRecord connectionRecord = activeConnectionRecordProvider.get();
connectionRecord.init(user, connectionGroup, connection);
GuacamoleTunnel tunnel = assignGuacamoleTunnel(connectionRecord, info, connections.size() > 1);
// If session affinity is enabled, prefer this connection going forward
if (connectionGroup.isSessionAffinityEnabled())
user.preferConnection(connection.getIdentifier());
return tunnel;
}
// If connection failed due to an upstream error, retry other
// connections
catch (GuacamoleUpstreamException e) {
logger.info("Upstream error intercepted for connection \"{}\". Failing over to next connection in group...", connection.getIdentifier());
logger.debug("Upstream remote desktop reported an error during connection.", e);
connections.remove(connection);
}
} while (!connections.isEmpty());
// All connection possibilities have been exhausted
throw new GuacamoleResourceConflictException("Cannot connect. All upstream connections are unavailable.");
} }
@@ -703,7 +741,7 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS
definition.getSharingProfile()); definition.getSharingProfile());
// Connect to shared connection described by the created record // Connect to shared connection described by the created record
GuacamoleTunnel tunnel = assignGuacamoleTunnel(connectionRecord, info); GuacamoleTunnel tunnel = assignGuacamoleTunnel(connectionRecord, info, false);
// Register tunnel, such that it is closed when the // Register tunnel, such that it is closed when the
// SharedConnectionDefinition is invalidated // SharedConnectionDefinition is invalidated