GUACAMOLE-317: Merge Add support for failover-only connections.

This commit is contained in:
Nick Couchman
2017-06-15 21:05:36 -04:00
11 changed files with 136 additions and 22 deletions

View File

@@ -61,6 +61,13 @@ public class ConnectionModel extends ChildObjectModel {
*/
private Integer connectionWeight;
/**
* Whether this connection should be reserved for failover. Failover-only
* connections within a balancing group are only used when all non-failover
* connections are unavailable.
*/
private boolean failoverOnly;
/**
* The identifiers of all readable sharing profiles associated with this
* connection.
@@ -196,6 +203,32 @@ public class ConnectionModel extends ChildObjectModel {
return connectionWeight;
}
/**
* Returns whether this connection should be reserved for failover.
* Failover-only connections within a balancing group are only used when
* all non-failover connections are unavailable.
*
* @return
* true if this connection should be reserved for failover, false
* otherwise.
*/
public boolean isFailoverOnly() {
return failoverOnly;
}
/**
* Sets whether this connection should be reserved for failover.
* Failover-only connections within a balancing group are only used when
* all non-failover connections are unavailable.
*
* @param failoverOnly
* true if this connection should be reserved for failover, false
* otherwise.
*/
public void setFailoverOnly(boolean failoverOnly) {
this.failoverOnly = failoverOnly;
}
/**
* Sets the maximum number of connections that can be established to this
* connection concurrently by any one user.

View File

@@ -32,6 +32,7 @@ import org.apache.guacamole.auth.jdbc.tunnel.GuacamoleTunnelService;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.jdbc.JDBCEnvironment;
import org.apache.guacamole.auth.jdbc.base.ModeledChildDirectoryObject;
import org.apache.guacamole.form.BooleanField;
import org.apache.guacamole.form.EnumField;
import org.apache.guacamole.form.Field;
import org.apache.guacamole.form.Form;
@@ -121,6 +122,13 @@ public class ModeledConnection extends ModeledChildDirectoryObject<ConnectionMod
*/
public static final String CONNECTION_WEIGHT = "weight";
/**
* The name of the attribute which controls whether the connection should
* be used as a spare only (all other non-spare connections within the same
* balancing group should be preferred).
*/
public static final String FAILOVER_ONLY_NAME = "failover-only";
/**
* All attributes related to restricting user accounts, within a logical
* form.
@@ -134,7 +142,8 @@ public class ModeledConnection extends ModeledChildDirectoryObject<ConnectionMod
* All attributes related to load balancing in a logical form.
*/
public static final Form LOAD_BALANCING = new Form("load-balancing", Arrays.<Field>asList(
new NumericField(CONNECTION_WEIGHT)
new NumericField(CONNECTION_WEIGHT),
new BooleanField(FAILOVER_ONLY_NAME, "true")
));
/**
@@ -281,6 +290,9 @@ public class ModeledConnection extends ModeledChildDirectoryObject<ConnectionMod
// Set connection weight
attributes.put(CONNECTION_WEIGHT, NumericField.format(getModel().getConnectionWeight()));
// Set whether connection is failover-only
attributes.put(FAILOVER_ONLY_NAME, getModel().isFailoverOnly() ? "true" : null);
return attributes;
}
@@ -333,6 +345,9 @@ public class ModeledConnection extends ModeledChildDirectoryObject<ConnectionMod
logger.debug("Unable to parse numeric attribute.", e);
}
// Translate failover-only attribute
getModel().setFailoverOnly("true".equals(attributes.get(FAILOVER_ONLY_NAME)));
}
/**
@@ -436,4 +451,17 @@ public class ModeledConnection extends ModeledChildDirectoryObject<ConnectionMod
}
/**
* Returns whether this connection should be reserved for failover.
* Failover-only connections within a balancing group are only used when
* all non-failover connections are unavailable.
*
* @return
* true if this connection should be reserved for failover, false
* otherwise.
*/
public boolean isFailoverOnly() {
return getModel().isFailoverOnly();
}
}

View File

@@ -44,7 +44,6 @@ import org.apache.guacamole.GuacamoleResourceNotFoundException;
import org.apache.guacamole.GuacamoleSecurityException;
import org.apache.guacamole.GuacamoleServerException;
import org.apache.guacamole.GuacamoleUpstreamException;
import org.apache.guacamole.auth.jdbc.JDBCEnvironment;
import org.apache.guacamole.auth.jdbc.connection.ConnectionMapper;
import org.apache.guacamole.net.GuacamoleSocket;
import org.apache.guacamole.net.GuacamoleTunnel;
@@ -143,6 +142,10 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS
* @param connections
* The connections being accessed.
*
* @param includeFailoverOnly
* Whether connections which have been designated for use in failover
* situations only (hot spares) may be considered.
*
* @return
* The connection that has been acquired on behalf of the given user.
*
@@ -150,7 +153,8 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS
* If access is denied to the given user for any reason.
*/
protected abstract ModeledConnection acquire(RemoteAuthenticatedUser user,
List<ModeledConnection> connections) throws GuacamoleException;
List<ModeledConnection> connections, boolean includeFailoverOnly)
throws GuacamoleException;
/**
* Releases possibly-exclusive access to the given connection on behalf of
@@ -647,8 +651,8 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS
final ModeledConnection connection, GuacamoleClientInformation info)
throws GuacamoleException {
// Acquire access to single connection
acquire(user, Collections.singletonList(connection));
// Acquire access to single connection, ignoring the failover-only flag
acquire(user, Collections.singletonList(connection), true);
// Connect only if the connection was successfully acquired
ActiveConnectionRecord connectionRecord = activeConnectionRecordProvider.get();
@@ -668,6 +672,9 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS
ModeledConnectionGroup connectionGroup,
GuacamoleClientInformation info) throws GuacamoleException {
// Track failures in upstream (remote desktop) connections
boolean upstreamHasFailed = false;
// If group has no associated balanced connections, cannot connect
List<ModeledConnection> connections = getBalancedConnections(user, connectionGroup);
if (connections.isEmpty())
@@ -678,10 +685,11 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS
// Acquire group
acquire(user, connectionGroup);
// Attempt to acquire to any child
// Attempt to acquire to any child, including failover-only
// connections only if at least one upstream failure has occurred
ModeledConnection connection;
try {
connection = acquire(user, connections);
connection = acquire(user, connections, upstreamHasFailed);
}
// Ensure connection group is always released if child acquire fails
@@ -701,6 +709,14 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS
if (connectionGroup.isSessionAffinityEnabled())
user.preferConnection(connection.getIdentifier());
// Warn if we are connecting to a failover-only connection
if (connection.isFailoverOnly())
logger.warn("One or more normal connections within "
+ "group \"{}\" have failed. Some connection "
+ "attempts are being routed to designated "
+ "failover-only connections.",
connectionGroup.getIdentifier());
return tunnel;
}
@@ -711,6 +727,7 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS
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);
upstreamHasFailed = true;
}
} while (!connections.isEmpty());

View File

@@ -171,7 +171,8 @@ public class RestrictedGuacamoleTunnelService
@Override
protected ModeledConnection acquire(RemoteAuthenticatedUser user,
List<ModeledConnection> connections) throws GuacamoleException {
List<ModeledConnection> connections, boolean includeFailoverOnly)
throws GuacamoleException {
// Do not acquire connection unless within overall limits
if (!tryIncrement(totalActiveConnections, environment.getAbsoluteMaxConnections()))
@@ -222,6 +223,11 @@ public class RestrictedGuacamoleTunnelService
continue;
}
// Skip connections which are failover-only if they are excluded
// from this connection attempt
if (!includeFailoverOnly && connection.isFailoverOnly())
continue;
// Attempt to aquire connection according to per-user limits
Seat seat = new Seat(username, connection.getIdentifier());
if (tryAdd(activeSeats, seat,

View File

@@ -20,6 +20,7 @@
"FIELD_HEADER_MAX_CONNECTIONS" : "Maximum number of connections:",
"FIELD_HEADER_MAX_CONNECTIONS_PER_USER" : "Maximum number of connections per user:",
"FIELD_HEADER_FAILOVER_ONLY" : "Use for failover only:",
"FIELD_HEADER_WEIGHT" : "Connection weight:",
"FIELD_HEADER_GUACD_HOSTNAME" : "Hostname:",

View File

@@ -65,9 +65,9 @@ CREATE TABLE `guacamole_connection` (
`max_connections` int(11),
`max_connections_per_user` int(11),
-- Connection weight
-- Load-balancing behavior
`connection_weight` int(11),
`failover_only` boolean NOT NULL DEFAULT 0,
PRIMARY KEY (`connection_id`),
UNIQUE KEY `connection_name_parent` (`connection_name`, `parent_id`),

View File

@@ -23,3 +23,10 @@
ALTER TABLE guacamole_connection
ADD COLUMN connection_weight int(11);
--
-- Add failover-only flag
--
ALTER TABLE guacamole_connection
ADD COLUMN failover_only BOOLEAN NOT NULL DEFAULT 0;

View File

@@ -38,6 +38,7 @@
<result column="proxy_encryption_method" property="proxyEncryptionMethod" jdbcType="VARCHAR"
javaType="org.apache.guacamole.net.auth.GuacamoleProxyConfiguration$EncryptionMethod"/>
<result column="connection_weight" property="connectionWeight" jdbcType="INTEGER"/>
<result column="failover_only" property="failoverOnly" jdbcType="BOOLEAN"/>
<!-- Associated sharing profiles -->
<collection property="sharingProfileIdentifiers" resultSet="sharingProfiles" ofType="java.lang.String"
@@ -97,7 +98,8 @@
proxy_hostname,
proxy_port,
proxy_encryption_method,
connection_weight
connection_weight,
failover_only
FROM guacamole_connection
WHERE connection_id IN
<foreach collection="identifiers" item="identifier"
@@ -129,7 +131,8 @@
proxy_hostname,
proxy_port,
proxy_encryption_method,
connection_weight
connection_weight,
failover_only
FROM guacamole_connection
JOIN guacamole_connection_permission ON guacamole_connection_permission.connection_id = guacamole_connection.connection_id
WHERE guacamole_connection.connection_id IN
@@ -166,7 +169,8 @@
proxy_hostname,
proxy_port,
proxy_encryption_method,
connection_weight
connection_weight,
failover_only
FROM guacamole_connection
WHERE
<if test="parentIdentifier != null">parent_id = #{parentIdentifier,jdbcType=VARCHAR}</if>
@@ -194,7 +198,8 @@
proxy_hostname,
proxy_port,
proxy_encryption_method,
connection_weight
connection_weight,
failover_only
)
VALUES (
#{object.name,jdbcType=VARCHAR},
@@ -205,7 +210,8 @@
#{object.proxyHostname,jdbcType=VARCHAR},
#{object.proxyPort,jdbcType=INTEGER},
#{object.proxyEncryptionMethod,jdbcType=VARCHAR},
#{object.connectionWeight,jdbcType=INTEGER}
#{object.connectionWeight,jdbcType=INTEGER},
#{object.failoverOnly,jdbcType=BOOLEAN}
)
</insert>
@@ -221,7 +227,8 @@
proxy_hostname = #{object.proxyHostname,jdbcType=VARCHAR},
proxy_port = #{object.proxyPort,jdbcType=INTEGER},
proxy_encryption_method = #{object.proxyEncryptionMethod,jdbcType=VARCHAR},
connection_weight = #{object.connectionWeight,jdbcType=INTEGER}
connection_weight = #{object.connectionWeight,jdbcType=INTEGER},
failover_only = #{object.failoverOnly,jdbcType=BOOLEAN}
WHERE connection_id = #{object.objectID,jdbcType=INTEGER}
</update>

View File

@@ -108,6 +108,7 @@ CREATE TABLE guacamole_connection (
-- Connection Weight
connection_weight integer,
failover_only boolean NOT NULL DEFAULT FALSE,
-- Guacamole proxy (guacd) overrides
proxy_port integer,

View File

@@ -23,3 +23,10 @@
ALTER TABLE guacamole_connection
ADD COLUMN connection_weight int;
--
-- Add failover-only flag
--
ALTER TABLE guacamole_connection
ADD COLUMN failover_only BOOLEAN NOT NULL DEFAULT FALSE;

View File

@@ -38,6 +38,7 @@
<result column="proxy_encryption_method" property="proxyEncryptionMethod" jdbcType="VARCHAR"
javaType="org.apache.guacamole.net.auth.GuacamoleProxyConfiguration$EncryptionMethod"/>
<result column="connection_weight" property="connectionWeight" jdbcType="INTEGER"/>
<result column="failover_only" property="failoverOnly" jdbcType="BOOLEAN"/>
<!-- Associated sharing profiles -->
<collection property="sharingProfileIdentifiers" resultSet="sharingProfiles" ofType="java.lang.String"
@@ -97,7 +98,8 @@
proxy_hostname,
proxy_port,
proxy_encryption_method,
connection_weight
connection_weight,
failover_only
FROM guacamole_connection
WHERE connection_id IN
<foreach collection="identifiers" item="identifier"
@@ -129,7 +131,8 @@
proxy_hostname,
proxy_port,
proxy_encryption_method,
connection_weight
connection_weight,
failover_only
FROM guacamole_connection
JOIN guacamole_connection_permission ON guacamole_connection_permission.connection_id = guacamole_connection.connection_id
WHERE guacamole_connection.connection_id IN
@@ -166,7 +169,8 @@
proxy_hostname,
proxy_port,
proxy_encryption_method,
connection_weight
connection_weight,
failover_only
FROM guacamole_connection
WHERE
<if test="parentIdentifier != null">parent_id = #{parentIdentifier,jdbcType=INTEGER}::integer</if>
@@ -194,7 +198,8 @@
proxy_hostname,
proxy_port,
proxy_encryption_method,
connection_weight
connection_weight,
failover_only
)
VALUES (
#{object.name,jdbcType=VARCHAR},
@@ -205,7 +210,8 @@
#{object.proxyHostname,jdbcType=VARCHAR},
#{object.proxyPort,jdbcType=INTEGER},
#{object.proxyEncryptionMethod,jdbcType=VARCHAR}::guacamole_proxy_encryption_method,
#{object.connectionWeight,jdbcType=INTEGER}
#{object.connectionWeight,jdbcType=INTEGER},
#{object.failoverOnly,jdbcType=BOOLEAN}
)
</insert>
@@ -221,7 +227,8 @@
proxy_hostname = #{object.proxyHostname,jdbcType=VARCHAR},
proxy_port = #{object.proxyPort,jdbcType=INTEGER},
proxy_encryption_method = #{object.proxyEncryptionMethod,jdbcType=VARCHAR}::guacamole_proxy_encryption_method,
connection_weight = #{object.connectionWeight,jdbcType=INTEGER}
connection_weight = #{object.connectionWeight,jdbcType=INTEGER},
failover_only = #{object.failoverOnly,jdbcType=BOOLEAN}
WHERE connection_id = #{object.objectID,jdbcType=INTEGER}::integer
</update>