Merge pull request #247 from glyptodon/concurrent-policy

GUAC-830: Generalize tunnel services
This commit is contained in:
James Muehlner
2015-08-20 19:59:32 -07:00
9 changed files with 370 additions and 459 deletions

View File

@@ -90,7 +90,14 @@
<artifactId>guice-multibindings</artifactId> <artifactId>guice-multibindings</artifactId>
<version>3.0</version> <version>3.0</version>
</dependency> </dependency>
<!-- Guava - Utility Library -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -82,10 +82,9 @@ public class JDBCAuthenticationProviderModule extends MyBatisModule {
private final Environment environment; private final Environment environment;
/** /**
* The service class to use to provide GuacamoleSockets for each * The service to use to provide GuacamoleTunnels for each connection.
* connection.
*/ */
private final Class<? extends GuacamoleTunnelService> tunnelServiceClass; private final GuacamoleTunnelService tunnelService;
/** /**
* Creates a new JDBC authentication provider module that configures the * Creates a new JDBC authentication provider module that configures the
@@ -95,13 +94,13 @@ public class JDBCAuthenticationProviderModule extends MyBatisModule {
* @param environment * @param environment
* The environment to use to configure injected classes. * The environment to use to configure injected classes.
* *
* @param tunnelServiceClass * @param tunnelService
* The socket service to use to provide sockets for connections. * The tunnel service to use to provide tunnels sockets for connections.
*/ */
public JDBCAuthenticationProviderModule(Environment environment, public JDBCAuthenticationProviderModule(Environment environment,
Class<? extends GuacamoleTunnelService> tunnelServiceClass) { GuacamoleTunnelService tunnelService) {
this.environment = environment; this.environment = environment;
this.tunnelServiceClass = tunnelServiceClass; this.tunnelService = tunnelService;
} }
@Override @Override
@@ -156,8 +155,8 @@ public class JDBCAuthenticationProviderModule extends MyBatisModule {
bind(UserPermissionService.class); bind(UserPermissionService.class);
bind(UserService.class); bind(UserService.class);
// Bind provided socket service // Bind provided tunnel service
bind(GuacamoleTunnelService.class).to(tunnelServiceClass); bind(GuacamoleTunnelService.class).toInstance(tunnelService);
} }

View File

@@ -1,88 +0,0 @@
/*
* Copyright (C) 2015 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.glyptodon.guacamole.auth.jdbc.tunnel;
import com.google.inject.Singleton;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.glyptodon.guacamole.auth.jdbc.user.AuthenticatedUser;
import org.glyptodon.guacamole.auth.jdbc.connection.ModeledConnection;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.GuacamoleResourceConflictException;
import org.glyptodon.guacamole.auth.jdbc.connectiongroup.ModeledConnectionGroup;
/**
* GuacamoleTunnelService implementation which allows only one user per
* connection at any time, but does not disallow concurrent use of connection
* groups. If a user attempts to use a connection group multiple times, they
* will receive different underlying connections each time until the group is
* exhausted.
*
* @author Michael Jumper
*/
@Singleton
public class BalancedGuacamoleTunnelService
extends AbstractGuacamoleTunnelService {
/**
* The set of all active connection identifiers.
*/
private final Set<String> activeConnections =
Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
@Override
protected ModeledConnection acquire(AuthenticatedUser user,
List<ModeledConnection> connections) throws GuacamoleException {
// Return the first unused connection
for (ModeledConnection connection : connections) {
if (activeConnections.add(connection.getIdentifier()))
return connection;
}
// Already in use
throw new GuacamoleResourceConflictException("Cannot connect. This connection is in use.");
}
@Override
protected void release(AuthenticatedUser user, ModeledConnection connection) {
activeConnections.remove(connection.getIdentifier());
}
@Override
protected void acquire(AuthenticatedUser user,
ModeledConnectionGroup connectionGroup) throws GuacamoleException {
// Do nothing
}
@Override
protected void release(AuthenticatedUser user,
ModeledConnectionGroup connectionGroup) {
// Do nothing
}
}

View File

@@ -0,0 +1,276 @@
/*
* Copyright (C) 2015 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.glyptodon.guacamole.auth.jdbc.tunnel;
import com.google.common.collect.ConcurrentHashMultiset;
import com.google.inject.Singleton;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import org.glyptodon.guacamole.GuacamoleClientTooManyException;
import org.glyptodon.guacamole.auth.jdbc.user.AuthenticatedUser;
import org.glyptodon.guacamole.auth.jdbc.connection.ModeledConnection;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.GuacamoleResourceConflictException;
import org.glyptodon.guacamole.auth.jdbc.connectiongroup.ModeledConnectionGroup;
/**
* GuacamoleTunnelService implementation which restricts concurrency for each
* connection and group according to a maximum number of connections and
* maximum number of connections per user.
*
* @author James Muehlner
* @author Michael Jumper
*/
@Singleton
public class ConfigurableGuacamoleTunnelService
extends AbstractGuacamoleTunnelService {
/**
* Set of all currently-active user/connection pairs (seats).
*/
private final ConcurrentHashMultiset<Seat> activeSeats = ConcurrentHashMultiset.<Seat>create();
/**
* Set of all currently-active connections.
*/
private final ConcurrentHashMultiset<String> activeConnections = ConcurrentHashMultiset.<String>create();
/**
* Set of all currently-active user/connection group pairs (seats).
*/
private final ConcurrentHashMultiset<Seat> activeGroupSeats = ConcurrentHashMultiset.<Seat>create();
/**
* Set of all currently-active connection groups.
*/
private final ConcurrentHashMultiset<String> activeGroups = ConcurrentHashMultiset.<String>create();
/**
* The maximum number of connections allowed per connection by default, or
* zero if no default limit applies.
*/
private final int connectionDefaultMaxConnections;
/**
* The maximum number of connections a user may have to any one connection
* by default, or zero if no default limit applies.
*/
private final int connectionDefaultMaxConnectionsPerUser;
/**
* The maximum number of connections allowed per connection group by
* default, or zero if no default limit applies.
*/
private final int connectionGroupDefaultMaxConnections;
/**
* The maximum number of connections a user may have to any one connection
* group by default, or zero if no default limit applies.
*/
private final int connectionGroupDefaultMaxConnectionsPerUser;
/**
* Creates a new ConfigurableGuacamoleTunnelService which applies the given
* limitations when new connections are acquired.
*
* @param connectionDefaultMaxConnections
* The maximum number of connections allowed per connection by default,
* or zero if no default limit applies.
*
* @param connectionDefaultMaxConnectionsPerUser
* The maximum number of connections a user may have to any one
* connection by default, or zero if no default limit applies.
*
* @param connectionGroupDefaultMaxConnections
* The maximum number of connections allowed per connection group by
* default, or zero if no default limit applies.
*
* @param connectionGroupDefaultMaxConnectionsPerUser
* The maximum number of connections a user may have to any one
* connection group by default, or zero if no default limit applies.
*/
public ConfigurableGuacamoleTunnelService(
int connectionDefaultMaxConnections,
int connectionDefaultMaxConnectionsPerUser,
int connectionGroupDefaultMaxConnections,
int connectionGroupDefaultMaxConnectionsPerUser) {
// Set default connection limits
this.connectionDefaultMaxConnections = connectionDefaultMaxConnections;
this.connectionDefaultMaxConnectionsPerUser = connectionDefaultMaxConnectionsPerUser;
// Set default connection group limits
this.connectionGroupDefaultMaxConnections = connectionGroupDefaultMaxConnections;
this.connectionGroupDefaultMaxConnectionsPerUser = connectionGroupDefaultMaxConnectionsPerUser;
}
/**
* Attempts to add a single instance of the given value to the given
* multiset without exceeding the specified maximum number of values. If
* the value cannot be added without exceeding the maximum, false is
* returned.
*
* @param <T>
* The type of values contained within the multiset.
*
* @param multiset
* The multiset to attempt to add a value to.
*
* @param value
* The value to attempt to add.
*
* @param max
* The maximum number of each distinct value that the given multiset
* should hold, or zero if no limit applies.
*
* @return
* true if the value was successfully added without exceeding the
* specified maximum, false if the value could not be added.
*/
private <T> boolean tryAdd(ConcurrentHashMultiset<T> multiset, T value, int max) {
// Repeatedly attempt to add a new value to the given multiset until we
// explicitly succeed or explicitly fail
while (true) {
// Get current number of values
int count = multiset.count(value);
// Bail out if the maximum has already been reached
if (count >= max || max == 0)
return false;
// Attempt to add one more value
if (multiset.setCount(value, count, count+1))
return true;
// Try again if unsuccessful
}
}
@Override
protected ModeledConnection acquire(AuthenticatedUser user,
List<ModeledConnection> connections) throws GuacamoleException {
// Get username
String username = user.getUser().getIdentifier();
// Sort connections in ascending order of usage
ModeledConnection[] sortedConnections = connections.toArray(new ModeledConnection[connections.size()]);
Arrays.sort(sortedConnections, new Comparator<ModeledConnection>() {
@Override
public int compare(ModeledConnection a, ModeledConnection b) {
return getActiveConnections(a).size()
- getActiveConnections(b).size();
}
});
// Track whether acquire fails due to user-specific limits
boolean userSpecificFailure = true;
// Return the first unreserved connection
for (ModeledConnection connection : sortedConnections) {
// Attempt to aquire connection according to per-user limits
Seat seat = new Seat(username, connection.getIdentifier());
if (tryAdd(activeSeats, seat,
connectionDefaultMaxConnectionsPerUser)) {
// Attempt to aquire connection according to overall limits
if (tryAdd(activeConnections, connection.getIdentifier(),
connectionDefaultMaxConnections))
return connection;
// Acquire failed - retry with next connection
activeSeats.remove(seat);
// Failure to acquire is not user-specific
userSpecificFailure = false;
}
}
// Too many connections by this user
if (userSpecificFailure)
throw new GuacamoleClientTooManyException("Cannot connect. Connection group already in use by this user.");
// Too many connections, but not necessarily due purely to this user
else
throw new GuacamoleResourceConflictException("Cannot connect. This connection is in use.");
}
@Override
protected void release(AuthenticatedUser user, ModeledConnection connection) {
activeSeats.remove(new Seat(user.getUser().getIdentifier(), connection.getIdentifier()));
activeConnections.remove(connection.getIdentifier());
}
@Override
protected void acquire(AuthenticatedUser user,
ModeledConnectionGroup connectionGroup) throws GuacamoleException {
// Get username
String username = user.getUser().getIdentifier();
// Attempt to aquire connection group according to per-user limits
Seat seat = new Seat(username, connectionGroup.getIdentifier());
if (tryAdd(activeGroupSeats, seat,
connectionGroupDefaultMaxConnectionsPerUser)) {
// Attempt to aquire connection group according to overall limits
if (tryAdd(activeGroups, connectionGroup.getIdentifier(),
connectionGroupDefaultMaxConnections))
return;
// Acquire failed
activeGroupSeats.remove(seat);
// Failure to acquire is not user-specific
throw new GuacamoleResourceConflictException("Cannot connect. This connection group is in use.");
}
// Already in use by this user
throw new GuacamoleClientTooManyException("Cannot connect. Connection group already in use by this user.");
}
@Override
protected void release(AuthenticatedUser user,
ModeledConnectionGroup connectionGroup) {
activeGroupSeats.remove(new Seat(user.getUser().getIdentifier(), connectionGroup.getIdentifier()));
activeGroups.remove(connectionGroup.getIdentifier());
}
}

View File

@@ -1,116 +0,0 @@
/*
* Copyright (C) 2015 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.glyptodon.guacamole.auth.jdbc.tunnel;
import com.google.inject.Singleton;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.glyptodon.guacamole.GuacamoleClientTooManyException;
import org.glyptodon.guacamole.auth.jdbc.user.AuthenticatedUser;
import org.glyptodon.guacamole.auth.jdbc.connection.ModeledConnection;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.GuacamoleResourceConflictException;
import org.glyptodon.guacamole.auth.jdbc.connectiongroup.ModeledConnectionGroup;
/**
* GuacamoleTunnelService implementation which restricts concurrency only on a
* per-user basis. Each connection or group may be used concurrently any number
* of times, but each concurrent use must be associated with a different user.
*
* @author Michael Jumper
*/
@Singleton
public class MultiseatGuacamoleTunnelService
extends AbstractGuacamoleTunnelService {
/**
* The set of all active user/connection pairs.
*/
private final Set<Seat> activeSeats =
Collections.newSetFromMap(new ConcurrentHashMap<Seat, Boolean>());
/**
* The set of all active user/connection group pairs.
*/
private final Set<Seat> activeGroupSeats =
Collections.newSetFromMap(new ConcurrentHashMap<Seat, Boolean>());
@Override
protected ModeledConnection acquire(AuthenticatedUser user,
List<ModeledConnection> connections) throws GuacamoleException {
String username = user.getUser().getIdentifier();
// Sort connections in ascending order of usage
ModeledConnection[] sortedConnections = connections.toArray(new ModeledConnection[connections.size()]);
Arrays.sort(sortedConnections, new Comparator<ModeledConnection>() {
@Override
public int compare(ModeledConnection a, ModeledConnection b) {
return getActiveConnections(a).size()
- getActiveConnections(b).size();
}
});
// Return the first unreserved connection
for (ModeledConnection connection : sortedConnections) {
if (activeSeats.add(new Seat(username, connection.getIdentifier())))
return connection;
}
// Already in use
throw new GuacamoleResourceConflictException("Cannot connect. This connection is in use.");
}
@Override
protected void release(AuthenticatedUser user, ModeledConnection connection) {
activeSeats.remove(new Seat(user.getUser().getIdentifier(), connection.getIdentifier()));
}
@Override
protected void acquire(AuthenticatedUser user,
ModeledConnectionGroup connectionGroup) throws GuacamoleException {
// Do not allow duplicate use of connection groups
Seat seat = new Seat(user.getUser().getIdentifier(), connectionGroup.getIdentifier());
if (!activeGroupSeats.add(seat))
throw new GuacamoleClientTooManyException("Cannot connect. Connection group already in use by this user.");
}
@Override
protected void release(AuthenticatedUser user,
ModeledConnectionGroup connectionGroup) {
activeGroupSeats.remove(new Seat(user.getUser().getIdentifier(), connectionGroup.getIdentifier()));
}
}

View File

@@ -1,99 +0,0 @@
/*
* Copyright (C) 2015 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.glyptodon.guacamole.auth.jdbc.tunnel;
import com.google.inject.Singleton;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.glyptodon.guacamole.GuacamoleClientTooManyException;
import org.glyptodon.guacamole.auth.jdbc.user.AuthenticatedUser;
import org.glyptodon.guacamole.auth.jdbc.connection.ModeledConnection;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.GuacamoleResourceConflictException;
import org.glyptodon.guacamole.auth.jdbc.connectiongroup.ModeledConnectionGroup;
/**
* GuacamoleTunnelService implementation which allows exactly one use
* of any connection at any time. Concurrent usage of connections is not
* allowed, and concurrent usage of connection groups is allowed only between
* different users.
*
* @author Michael Jumper
*/
@Singleton
public class SingleSeatGuacamoleTunnelService
extends AbstractGuacamoleTunnelService {
/**
* The set of all active connection identifiers.
*/
private final Set<String> activeConnections =
Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
/**
* The set of all active user/connection group pairs.
*/
private final Set<Seat> activeGroupSeats =
Collections.newSetFromMap(new ConcurrentHashMap<Seat, Boolean>());
@Override
protected ModeledConnection acquire(AuthenticatedUser user,
List<ModeledConnection> connections) throws GuacamoleException {
// Return the first unused connection
for (ModeledConnection connection : connections) {
if (activeConnections.add(connection.getIdentifier()))
return connection;
}
// Already in use
throw new GuacamoleResourceConflictException("Cannot connect. This connection is in use.");
}
@Override
protected void release(AuthenticatedUser user, ModeledConnection connection) {
activeConnections.remove(connection.getIdentifier());
}
@Override
protected void acquire(AuthenticatedUser user,
ModeledConnectionGroup connectionGroup) throws GuacamoleException {
// Do not allow duplicate use of connection groups
Seat seat = new Seat(user.getUser().getIdentifier(), connectionGroup.getIdentifier());
if (!activeGroupSeats.add(seat))
throw new GuacamoleClientTooManyException("Cannot connect. Connection group already in use by this user.");
}
@Override
protected void release(AuthenticatedUser user,
ModeledConnectionGroup connectionGroup) {
activeGroupSeats.remove(new Seat(user.getUser().getIdentifier(), connectionGroup.getIdentifier()));
}
}

View File

@@ -1,82 +0,0 @@
/*
* Copyright (C) 2015 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.glyptodon.guacamole.auth.jdbc.tunnel;
import com.google.inject.Singleton;
import java.util.List;
import org.glyptodon.guacamole.auth.jdbc.user.AuthenticatedUser;
import org.glyptodon.guacamole.auth.jdbc.connection.ModeledConnection;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.auth.jdbc.connectiongroup.ModeledConnectionGroup;
/**
* GuacamoleTunnelService implementation which imposes no restrictions
* whatsoever on the number of concurrent or duplicate connections.
*
* @author Michael Jumper
*/
@Singleton
public class UnrestrictedGuacamoleTunnelService
extends AbstractGuacamoleTunnelService {
@Override
protected ModeledConnection acquire(AuthenticatedUser user,
List<ModeledConnection> connections) throws GuacamoleException {
ModeledConnection chosen = null;
int lowestUsage = 0;
// Find connection with lowest usage
for (ModeledConnection connection : connections) {
int usage = getActiveConnections(connection).size();
if (chosen == null || usage < lowestUsage) {
chosen = connection;
lowestUsage = usage;
}
}
return chosen;
}
@Override
protected void release(AuthenticatedUser user, ModeledConnection connection) {
// Do nothing
}
@Override
protected void acquire(AuthenticatedUser user,
ModeledConnectionGroup connectionGroup) throws GuacamoleException {
// Do nothing
}
@Override
protected void release(AuthenticatedUser user,
ModeledConnectionGroup connectionGroup) {
// Do nothing
}
}

View File

@@ -30,10 +30,7 @@ import org.glyptodon.guacamole.net.auth.Credentials;
import org.glyptodon.guacamole.net.auth.UserContext; import org.glyptodon.guacamole.net.auth.UserContext;
import org.glyptodon.guacamole.auth.jdbc.JDBCAuthenticationProviderModule; import org.glyptodon.guacamole.auth.jdbc.JDBCAuthenticationProviderModule;
import org.glyptodon.guacamole.auth.jdbc.tunnel.GuacamoleTunnelService; import org.glyptodon.guacamole.auth.jdbc.tunnel.GuacamoleTunnelService;
import org.glyptodon.guacamole.auth.jdbc.tunnel.MultiseatGuacamoleTunnelService; import org.glyptodon.guacamole.auth.jdbc.tunnel.ConfigurableGuacamoleTunnelService;
import org.glyptodon.guacamole.auth.jdbc.tunnel.BalancedGuacamoleTunnelService;
import org.glyptodon.guacamole.auth.jdbc.tunnel.SingleSeatGuacamoleTunnelService;
import org.glyptodon.guacamole.auth.jdbc.tunnel.UnrestrictedGuacamoleTunnelService;
import org.glyptodon.guacamole.auth.jdbc.user.UserContextService; import org.glyptodon.guacamole.auth.jdbc.user.UserContextService;
import org.glyptodon.guacamole.environment.Environment; import org.glyptodon.guacamole.environment.Environment;
import org.glyptodon.guacamole.environment.LocalEnvironment; import org.glyptodon.guacamole.environment.LocalEnvironment;
@@ -54,52 +51,62 @@ public class MySQLAuthenticationProvider implements AuthenticationProvider {
private final Injector injector; private final Injector injector;
/** /**
* Returns the appropriate socket service class given the Guacamole * Returns the appropriate tunnel service given the Guacamole environment.
* environment. The class is chosen based on configuration options that * The service is configured based on configuration options that dictate
* dictate concurrent usage policy. * the default concurrent usage policy.
* *
* @param environment * @param environment
* The environment of the Guacamole server. * The environment of the Guacamole server.
* *
* @return * @return
* The socket service class that matches the concurrent usage policy * A tunnel service implementation configured according to the
* options set in the Guacamole environment. * concurrent usage policy options set in the Guacamole environment.
* *
* @throws GuacamoleException * @throws GuacamoleException
* If an error occurs while reading the configuration options. * If an error occurs while reading the configuration options.
*/ */
private Class<? extends GuacamoleTunnelService> private GuacamoleTunnelService getTunnelService(Environment environment)
getSocketServiceClass(Environment environment)
throws GuacamoleException { throws GuacamoleException {
// Read concurrency-related properties // Tunnel service default configuration
int connectionDefaultMaxConnections;
int connectionDefaultMaxConnectionsPerUser;
int connectionGroupDefaultMaxConnections;
int connectionGroupDefaultMaxConnectionsPerUser;
// Read legacy concurrency-related properties
boolean disallowSimultaneous = environment.getProperty(MySQLGuacamoleProperties.MYSQL_DISALLOW_SIMULTANEOUS_CONNECTIONS, false); boolean disallowSimultaneous = environment.getProperty(MySQLGuacamoleProperties.MYSQL_DISALLOW_SIMULTANEOUS_CONNECTIONS, false);
boolean disallowDuplicate = environment.getProperty(MySQLGuacamoleProperties.MYSQL_DISALLOW_DUPLICATE_CONNECTIONS, true); boolean disallowDuplicate = environment.getProperty(MySQLGuacamoleProperties.MYSQL_DISALLOW_DUPLICATE_CONNECTIONS, true);
if (disallowSimultaneous) { // Legacy properties to not affect max connections per group
connectionGroupDefaultMaxConnections = 0;
// Connections may not be used concurrently // Legacy "simultaneous" property dictates only the maximum number of
if (disallowDuplicate) // connections per connection
return SingleSeatGuacamoleTunnelService.class; if (disallowSimultaneous)
connectionDefaultMaxConnections = 1;
// Connections are reserved for a single user when in use else
else connectionDefaultMaxConnections = 0;
return BalancedGuacamoleTunnelService.class;
// Legacy "duplicate" property dictates whether connections and groups
// may be used concurrently only by different users
if (disallowDuplicate) {
connectionDefaultMaxConnectionsPerUser = 1;
connectionGroupDefaultMaxConnectionsPerUser = 1;
} }
else { else {
connectionDefaultMaxConnectionsPerUser = 0;
// Connections may be used concurrently, but only once per user connectionGroupDefaultMaxConnectionsPerUser = 0;
if (disallowDuplicate)
return MultiseatGuacamoleTunnelService.class;
// Connection use is not restricted
else
return UnrestrictedGuacamoleTunnelService.class;
} }
// Return service configured for specified default limits
return new ConfigurableGuacamoleTunnelService(
connectionDefaultMaxConnections,
connectionDefaultMaxConnectionsPerUser,
connectionGroupDefaultMaxConnections,
connectionGroupDefaultMaxConnectionsPerUser
);
} }
/** /**
@@ -123,7 +130,7 @@ public class MySQLAuthenticationProvider implements AuthenticationProvider {
new MySQLAuthenticationProviderModule(environment), new MySQLAuthenticationProviderModule(environment),
// Configure JDBC authentication core // Configure JDBC authentication core
new JDBCAuthenticationProviderModule(environment, getSocketServiceClass(environment)) new JDBCAuthenticationProviderModule(environment, getTunnelService(environment))
); );

View File

@@ -29,11 +29,8 @@ import org.glyptodon.guacamole.net.auth.AuthenticationProvider;
import org.glyptodon.guacamole.net.auth.Credentials; import org.glyptodon.guacamole.net.auth.Credentials;
import org.glyptodon.guacamole.net.auth.UserContext; import org.glyptodon.guacamole.net.auth.UserContext;
import org.glyptodon.guacamole.auth.jdbc.JDBCAuthenticationProviderModule; import org.glyptodon.guacamole.auth.jdbc.JDBCAuthenticationProviderModule;
import org.glyptodon.guacamole.auth.jdbc.tunnel.BalancedGuacamoleTunnelService; import org.glyptodon.guacamole.auth.jdbc.tunnel.ConfigurableGuacamoleTunnelService;
import org.glyptodon.guacamole.auth.jdbc.tunnel.GuacamoleTunnelService; import org.glyptodon.guacamole.auth.jdbc.tunnel.GuacamoleTunnelService;
import org.glyptodon.guacamole.auth.jdbc.tunnel.MultiseatGuacamoleTunnelService;
import org.glyptodon.guacamole.auth.jdbc.tunnel.SingleSeatGuacamoleTunnelService;
import org.glyptodon.guacamole.auth.jdbc.tunnel.UnrestrictedGuacamoleTunnelService;
import org.glyptodon.guacamole.auth.jdbc.user.UserContextService; import org.glyptodon.guacamole.auth.jdbc.user.UserContextService;
import org.glyptodon.guacamole.environment.Environment; import org.glyptodon.guacamole.environment.Environment;
import org.glyptodon.guacamole.environment.LocalEnvironment; import org.glyptodon.guacamole.environment.LocalEnvironment;
@@ -54,52 +51,62 @@ public class PostgreSQLAuthenticationProvider implements AuthenticationProvider
private final Injector injector; private final Injector injector;
/** /**
* Returns the appropriate socket service class given the Guacamole * Returns the appropriate tunnel service given the Guacamole environment.
* environment. The class is chosen based on configuration options that * The service is configured based on configuration options that dictate
* dictate concurrent usage policy. * the default concurrent usage policy.
* *
* @param environment * @param environment
* The environment of the Guacamole server. * The environment of the Guacamole server.
* *
* @return * @return
* The socket service class that matches the concurrent usage policy * A tunnel service implementation configured according to the
* options set in the Guacamole environment. * concurrent usage policy options set in the Guacamole environment.
* *
* @throws GuacamoleException * @throws GuacamoleException
* If an error occurs while reading the configuration options. * If an error occurs while reading the configuration options.
*/ */
private Class<? extends GuacamoleTunnelService> private GuacamoleTunnelService getTunnelService(Environment environment)
getSocketServiceClass(Environment environment)
throws GuacamoleException { throws GuacamoleException {
// Read concurrency-related properties // Tunnel service default configuration
int connectionDefaultMaxConnections;
int connectionDefaultMaxConnectionsPerUser;
int connectionGroupDefaultMaxConnections;
int connectionGroupDefaultMaxConnectionsPerUser;
// Read legacy concurrency-related properties
boolean disallowSimultaneous = environment.getProperty(PostgreSQLGuacamoleProperties.POSTGRESQL_DISALLOW_SIMULTANEOUS_CONNECTIONS, false); boolean disallowSimultaneous = environment.getProperty(PostgreSQLGuacamoleProperties.POSTGRESQL_DISALLOW_SIMULTANEOUS_CONNECTIONS, false);
boolean disallowDuplicate = environment.getProperty(PostgreSQLGuacamoleProperties.POSTGRESQL_DISALLOW_DUPLICATE_CONNECTIONS, true); boolean disallowDuplicate = environment.getProperty(PostgreSQLGuacamoleProperties.POSTGRESQL_DISALLOW_DUPLICATE_CONNECTIONS, true);
if (disallowSimultaneous) { // Legacy properties to not affect max connections per group
connectionGroupDefaultMaxConnections = 0;
// Connections may not be used concurrently // Legacy "simultaneous" property dictates only the maximum number of
if (disallowDuplicate) // connections per connection
return SingleSeatGuacamoleTunnelService.class; if (disallowSimultaneous)
connectionDefaultMaxConnections = 1;
// Connections are reserved for a single user when in use else
else connectionDefaultMaxConnections = 0;
return BalancedGuacamoleTunnelService.class;
// Legacy "duplicate" property dictates whether connections and groups
// may be used concurrently only by different users
if (disallowDuplicate) {
connectionDefaultMaxConnectionsPerUser = 1;
connectionGroupDefaultMaxConnectionsPerUser = 1;
} }
else { else {
connectionDefaultMaxConnectionsPerUser = 0;
// Connections may be used concurrently, but only once per user connectionGroupDefaultMaxConnectionsPerUser = 0;
if (disallowDuplicate)
return MultiseatGuacamoleTunnelService.class;
// Connection use is not restricted
else
return UnrestrictedGuacamoleTunnelService.class;
} }
// Return service configured for specified default limits
return new ConfigurableGuacamoleTunnelService(
connectionDefaultMaxConnections,
connectionDefaultMaxConnectionsPerUser,
connectionGroupDefaultMaxConnections,
connectionGroupDefaultMaxConnectionsPerUser
);
} }
/** /**
@@ -123,7 +130,7 @@ public class PostgreSQLAuthenticationProvider implements AuthenticationProvider
new PostgreSQLAuthenticationProviderModule(environment), new PostgreSQLAuthenticationProviderModule(environment),
// Configure JDBC authentication core // Configure JDBC authentication core
new JDBCAuthenticationProviderModule(environment, getSocketServiceClass(environment)) new JDBCAuthenticationProviderModule(environment, getTunnelService(environment))
); );