From 3c553b965c1d089feb91eb192090418ac6165b24 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 6 Jun 2017 13:29:22 -0700 Subject: [PATCH 1/6] GUACAMOLE-317: Add "failover_only" column to database schema for Guacamole connections. --- .../auth/jdbc/connection/ConnectionModel.java | 33 +++++++++++++++++++ .../schema/001-create-schema.sql | 4 +-- .../schema/upgrade/upgrade-pre-0.9.14.sql | 7 ++++ .../auth/jdbc/connection/ConnectionMapper.xml | 17 +++++++--- .../schema/001-create-schema.sql | 1 + .../schema/upgrade/upgrade-pre-0.9.14.sql | 7 ++++ .../auth/jdbc/connection/ConnectionMapper.xml | 19 +++++++---- 7 files changed, 75 insertions(+), 13 deletions(-) diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connection/ConnectionModel.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connection/ConnectionModel.java index 78cc885b4..788daa1b0 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connection/ConnectionModel.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connection/ConnectionModel.java @@ -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. diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/schema/001-create-schema.sql b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/schema/001-create-schema.sql index 2f0aa7303..515ea726b 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/schema/001-create-schema.sql +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/schema/001-create-schema.sql @@ -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`), diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/schema/upgrade/upgrade-pre-0.9.14.sql b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/schema/upgrade/upgrade-pre-0.9.14.sql index 2b14e27f3..14a016b1f 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/schema/upgrade/upgrade-pre-0.9.14.sql +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/schema/upgrade/upgrade-pre-0.9.14.sql @@ -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; diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/apache/guacamole/auth/jdbc/connection/ConnectionMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/apache/guacamole/auth/jdbc/connection/ConnectionMapper.xml index 2f778fa5f..03ffa431c 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/apache/guacamole/auth/jdbc/connection/ConnectionMapper.xml +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/apache/guacamole/auth/jdbc/connection/ConnectionMapper.xml @@ -38,6 +38,7 @@ + parent_id = #{parentIdentifier,jdbcType=VARCHAR} @@ -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} ) @@ -222,6 +228,7 @@ proxy_port = #{object.proxyPort,jdbcType=INTEGER}, proxy_encryption_method = #{object.proxyEncryptionMethod,jdbcType=VARCHAR}, connection_weight = #{object.connectionWeight,jdbcType=INTEGER} + failover_only = #{object.failoverOnly,jdbcType=BOOLEAN} WHERE connection_id = #{object.objectID,jdbcType=INTEGER} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/schema/001-create-schema.sql b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/schema/001-create-schema.sql index 7b9081ee6..d1b5bf5f9 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/schema/001-create-schema.sql +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/schema/001-create-schema.sql @@ -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, diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/schema/upgrade/upgrade-pre-0.9.14.sql b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/schema/upgrade/upgrade-pre-0.9.14.sql index 6388b445f..20882ed74 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/schema/upgrade/upgrade-pre-0.9.14.sql +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/schema/upgrade/upgrade-pre-0.9.14.sql @@ -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; diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/apache/guacamole/auth/jdbc/connection/ConnectionMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/apache/guacamole/auth/jdbc/connection/ConnectionMapper.xml index f53e43948..dd9265dfa 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/apache/guacamole/auth/jdbc/connection/ConnectionMapper.xml +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/apache/guacamole/auth/jdbc/connection/ConnectionMapper.xml @@ -38,6 +38,7 @@ + parent_id = #{parentIdentifier,jdbcType=INTEGER}::integer @@ -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} ) @@ -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 From 31519061dc8eacc6efd7fb8a3a02c7ecc1bfb37e Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 6 Jun 2017 13:31:29 -0700 Subject: [PATCH 2/6] GUACAMOLE-317: Expose "failover-only" attribute for connections. --- .../auth/jdbc/connection/ModeledConnection.java | 17 ++++++++++++++++- .../src/main/resources/translations/en.json | 1 + 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connection/ModeledConnection.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connection/ModeledConnection.java index 3d6e625b0..e2cafe1af 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connection/ModeledConnection.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connection/ModeledConnection.java @@ -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 ModeledChildDirectoryObjectasList( - new NumericField(CONNECTION_WEIGHT) + new NumericField(CONNECTION_WEIGHT), + new BooleanField(FAILOVER_ONLY_NAME, "true") )); /** @@ -281,6 +290,9 @@ public class ModeledConnection extends ModeledChildDirectoryObject Date: Tue, 6 Jun 2017 13:36:56 -0700 Subject: [PATCH 3/6] GUACAMOLE-317: Add convenience getter for failover-only attribute at Connection level. --- .../auth/jdbc/connection/ModeledConnection.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connection/ModeledConnection.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connection/ModeledConnection.java index e2cafe1af..7f93de062 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connection/ModeledConnection.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connection/ModeledConnection.java @@ -451,4 +451,17 @@ public class ModeledConnection extends ModeledChildDirectoryObject Date: Tue, 6 Jun 2017 13:37:16 -0700 Subject: [PATCH 4/6] GUACAMOLE-317: Always prefer non-failover connections relative to failover-only connections. --- .../jdbc/tunnel/RestrictedGuacamoleTunnelService.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/RestrictedGuacamoleTunnelService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/RestrictedGuacamoleTunnelService.java index fa2c99f23..19a625ab2 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/RestrictedGuacamoleTunnelService.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/RestrictedGuacamoleTunnelService.java @@ -187,6 +187,15 @@ public class RestrictedGuacamoleTunnelService @Override public int compare(ModeledConnection a, ModeledConnection b) { + // Always prefer non-failover connections to those which are + // failover-only + if (a.isFailoverOnly()) { + if (!b.isFailoverOnly()) + return 1; + } + else if (b.isFailoverOnly()) + return -1; + // Active connections int connA = getActiveConnections(a).size(); int connB = getActiveConnections(b).size(); From 1b35d437836a6cf3b1488420477fd7ffb6aaaa32 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 6 Jun 2017 15:52:55 -0700 Subject: [PATCH 5/6] GUACAMOLE-317: Use failover-only connections for failover only. Do not balance to failover-only connections unless an upstream failure has occurred. --- .../AbstractGuacamoleTunnelService.java | 29 +++++++++++++++---- .../RestrictedGuacamoleTunnelService.java | 17 +++++------ 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/AbstractGuacamoleTunnelService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/AbstractGuacamoleTunnelService.java index 392d01944..40543338e 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/AbstractGuacamoleTunnelService.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/AbstractGuacamoleTunnelService.java @@ -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 connections) throws GuacamoleException; + List 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 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()); diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/RestrictedGuacamoleTunnelService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/RestrictedGuacamoleTunnelService.java index 19a625ab2..a5d509b9d 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/RestrictedGuacamoleTunnelService.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/RestrictedGuacamoleTunnelService.java @@ -171,7 +171,8 @@ public class RestrictedGuacamoleTunnelService @Override protected ModeledConnection acquire(RemoteAuthenticatedUser user, - List connections) throws GuacamoleException { + List connections, boolean includeFailoverOnly) + throws GuacamoleException { // Do not acquire connection unless within overall limits if (!tryIncrement(totalActiveConnections, environment.getAbsoluteMaxConnections())) @@ -187,15 +188,6 @@ public class RestrictedGuacamoleTunnelService @Override public int compare(ModeledConnection a, ModeledConnection b) { - // Always prefer non-failover connections to those which are - // failover-only - if (a.isFailoverOnly()) { - if (!b.isFailoverOnly()) - return 1; - } - else if (b.isFailoverOnly()) - return -1; - // Active connections int connA = getActiveConnections(a).size(); int connB = getActiveConnections(b).size(); @@ -231,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, From 06a090d7d6d7fcf5a6143cf863db5c3053854fd2 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 13 Jun 2017 22:39:50 -0700 Subject: [PATCH 6/6] GUACAMOLE-317: Add missing comma to MySQL ConnectionMapper.xml. --- .../apache/guacamole/auth/jdbc/connection/ConnectionMapper.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/apache/guacamole/auth/jdbc/connection/ConnectionMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/apache/guacamole/auth/jdbc/connection/ConnectionMapper.xml index 03ffa431c..97c2e544b 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/apache/guacamole/auth/jdbc/connection/ConnectionMapper.xml +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/apache/guacamole/auth/jdbc/connection/ConnectionMapper.xml @@ -227,7 +227,7 @@ 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}