| | |
| | | import java.io.IOException; |
| | | import java.net.*; |
| | | import java.util.*; |
| | | import java.util.concurrent.ConcurrentLinkedQueue; |
| | | import java.util.concurrent.Semaphore; |
| | | import java.util.concurrent.*; |
| | | import java.util.concurrent.atomic.AtomicInteger; |
| | | import java.util.concurrent.locks.ReentrantReadWriteLock; |
| | | import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; |
| | |
| | | import org.opends.messages.Message; |
| | | import org.opends.server.admin.server.ConfigurationChangeListener; |
| | | import org.opends.server.admin.std.server.*; |
| | | import org.opends.server.api.AuthenticationPolicy; |
| | | import org.opends.server.api.AuthenticationPolicyFactory; |
| | | import org.opends.server.api.AuthenticationPolicyState; |
| | | import org.opends.server.api.TrustManagerProvider; |
| | | import org.opends.server.api.*; |
| | | import org.opends.server.config.ConfigException; |
| | | import org.opends.server.core.DirectoryServer; |
| | | import org.opends.server.loggers.debug.DebugLogger; |
| | |
| | | AuthenticationPolicyFactory<LDAPPassThroughAuthenticationPolicyCfg> |
| | | { |
| | | |
| | | // TODO: retry operations transparently until all connections exhausted. |
| | | // TODO: handle password policy response controls? AD? |
| | | // TODO: periodically ping offline servers in order to detect when they come |
| | | // back. |
| | | // TODO: provide alternative cfg for search password. |
| | | // TODO: custom aliveness pings |
| | | // TODO: manage account lockout |
| | | // TODO: cache password |
| | | |
| | | /** |
| | | * A simplistic load-balancer connection factory implementation using |
| | | * approximately round-robin balancing. |
| | | */ |
| | | static abstract class AbstractLoadBalancer implements ConnectionFactory, |
| | | Runnable |
| | | { |
| | | /** |
| | | * A connection which automatically retries operations on other servers. |
| | | */ |
| | | private final class FailoverConnection implements Connection |
| | | { |
| | | private Connection connection; |
| | | private MonitoredConnectionFactory factory; |
| | | private final int startIndex; |
| | | private int nextIndex; |
| | | |
| | | |
| | | |
| | | private FailoverConnection(final int startIndex) |
| | | throws DirectoryException |
| | | { |
| | | this.startIndex = nextIndex = startIndex; |
| | | |
| | | DirectoryException lastException = null; |
| | | do |
| | | { |
| | | factory = factories[nextIndex]; |
| | | if (factory.isAvailable()) |
| | | { |
| | | try |
| | | { |
| | | if (factory.isAvailable) |
| | | { |
| | | connection = factory.getConnection(); |
| | | incrementNextIndex(); |
| | | return; |
| | | } |
| | | } |
| | | catch (final DirectoryException e) |
| | | { |
| | | // Ignore this error and try the next factory. |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, e); |
| | | } |
| | | lastException = e; |
| | | } |
| | | } |
| | | incrementNextIndex(); |
| | | } |
| | | while (nextIndex != startIndex); |
| | | |
| | | // All the factories have been tried so give up and throw the exception. |
| | | throw lastException; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public void close() |
| | | { |
| | | connection.close(); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public ByteString search(final DN baseDN, final SearchScope scope, |
| | | final SearchFilter filter) throws DirectoryException |
| | | { |
| | | for (;;) |
| | | { |
| | | try |
| | | { |
| | | return connection.search(baseDN, scope, filter); |
| | | } |
| | | catch (final DirectoryException e) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, e); |
| | | } |
| | | handleDirectoryException(e); |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public void simpleBind(final ByteString username, |
| | | final ByteString password) throws DirectoryException |
| | | { |
| | | for (;;) |
| | | { |
| | | try |
| | | { |
| | | connection.simpleBind(username, password); |
| | | return; |
| | | } |
| | | catch (final DirectoryException e) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, e); |
| | | } |
| | | handleDirectoryException(e); |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | private void handleDirectoryException(final DirectoryException e) |
| | | throws DirectoryException |
| | | { |
| | | // If the error does not indicate that the connection has failed, then |
| | | // pass this back to the caller. |
| | | if (!isFatalResultCode(e.getResultCode())) |
| | | { |
| | | throw e; |
| | | } |
| | | |
| | | // The associated server is unavailable, so close the connection and |
| | | // try the next connection factory. |
| | | connection.close(); |
| | | factory.isAvailable = false; |
| | | |
| | | while (nextIndex != startIndex) |
| | | { |
| | | factory = factories[nextIndex]; |
| | | if (factory.isAvailable()) |
| | | { |
| | | try |
| | | { |
| | | if (factory.isAvailable) |
| | | { |
| | | connection = factory.getConnection(); |
| | | incrementNextIndex(); |
| | | return; |
| | | } |
| | | } |
| | | catch (final DirectoryException de) |
| | | { |
| | | // Ignore this error and try the next factory. |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, de); |
| | | } |
| | | } |
| | | } |
| | | incrementNextIndex(); |
| | | } |
| | | |
| | | // All the factories have been tried so give up and throw the exception. |
| | | throw e; |
| | | } |
| | | |
| | | |
| | | |
| | | private void incrementNextIndex() |
| | | { |
| | | // Try the next index. |
| | | if (++nextIndex == maxIndex) |
| | | { |
| | | nextIndex = 0; |
| | | } |
| | | } |
| | | |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * A connection factory which caches its online/offline state in order to |
| | | * avoid unnecessary connection attempts when it is known to be offline. |
| | | */ |
| | | private final class MonitoredConnectionFactory implements ConnectionFactory |
| | | { |
| | | private final ConnectionFactory factory; |
| | | private volatile boolean isAvailable = true; |
| | | |
| | | |
| | | |
| | | private MonitoredConnectionFactory(final ConnectionFactory factory) |
| | | { |
| | | this.factory = factory; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public void close() |
| | | { |
| | | factory.close(); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public Connection getConnection() throws DirectoryException |
| | | { |
| | | try |
| | | { |
| | | final Connection connection = factory.getConnection(); |
| | | isAvailable = true; |
| | | return connection; |
| | | } |
| | | catch (final DirectoryException e) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, e); |
| | | } |
| | | isAvailable = false; |
| | | throw e; |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | private boolean isAvailable() |
| | | { |
| | | return isAvailable; |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | private final MonitoredConnectionFactory[] factories; |
| | | private final int maxIndex; |
| | | private final ScheduledFuture<?> monitorFuture; |
| | | |
| | | |
| | | |
| | | /** |
| | | * Creates a new abstract load-balancer. |
| | | * |
| | | * @param factories |
| | | * The list of underlying connection factories. |
| | | * @param scheduler |
| | | * The monitoring scheduler. |
| | | */ |
| | | AbstractLoadBalancer(final ConnectionFactory[] factories, |
| | | final ScheduledExecutorService scheduler) |
| | | { |
| | | this.factories = new MonitoredConnectionFactory[factories.length]; |
| | | this.maxIndex = factories.length; |
| | | |
| | | for (int i = 0; i < maxIndex; i++) |
| | | { |
| | | this.factories[i] = new MonitoredConnectionFactory(factories[i]); |
| | | } |
| | | |
| | | this.monitorFuture = scheduler.scheduleWithFixedDelay(this, 5, 5, |
| | | TimeUnit.SECONDS); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Close underlying connection pools. |
| | | */ |
| | | @Override |
| | | public final void close() |
| | | { |
| | | monitorFuture.cancel(true); |
| | | |
| | | for (final ConnectionFactory factory : factories) |
| | | { |
| | | factory.close(); |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public final Connection getConnection() throws DirectoryException |
| | | { |
| | | final int startIndex = getStartIndex(); |
| | | return new FailoverConnection(startIndex); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Try to connect to any offline connection factories. |
| | | */ |
| | | @Override |
| | | public void run() |
| | | { |
| | | for (final MonitoredConnectionFactory factory : factories) |
| | | { |
| | | if (!factory.isAvailable()) |
| | | { |
| | | try |
| | | { |
| | | factory.getConnection().close(); |
| | | } |
| | | catch (final DirectoryException e) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, e); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Return the start which should be used for the next connection attempt. |
| | | * |
| | | * @return The start which should be used for the next connection attempt. |
| | | */ |
| | | abstract int getStartIndex(); |
| | | |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * A factory which returns pre-authenticated connections for searches. |
| | |
| | | * <p> |
| | | * Package private for testing. |
| | | */ |
| | | static final class ConnectionPool implements ConnectionFactory, Closeable |
| | | static final class ConnectionPool implements ConnectionFactory |
| | | { |
| | | |
| | | /** |
| | |
| | | * <p> |
| | | * Package private for testing. |
| | | */ |
| | | static final class FailoverConnectionFactory implements ConnectionFactory, |
| | | Closeable |
| | | static final class FailoverLoadBalancer extends AbstractLoadBalancer |
| | | { |
| | | private final ConnectionFactory primary; |
| | | private final ConnectionFactory secondary; |
| | | |
| | | |
| | | |
| | | /** |
| | | * Creates a new fail-over connection factory which will always try the |
| | |
| | | * The primary connection factory. |
| | | * @param secondary |
| | | * The secondary connection factory. |
| | | * @param scheduler |
| | | * The monitoring scheduler. |
| | | */ |
| | | FailoverConnectionFactory(final ConnectionFactory primary, |
| | | final ConnectionFactory secondary) |
| | | FailoverLoadBalancer(final ConnectionFactory primary, |
| | | final ConnectionFactory secondary, |
| | | final ScheduledExecutorService scheduler) |
| | | { |
| | | this.primary = primary; |
| | | this.secondary = secondary; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Close underlying load-balancers. |
| | | */ |
| | | @Override |
| | | public void close() |
| | | { |
| | | primary.close(); |
| | | if (secondary != null) |
| | | { |
| | | secondary.close(); |
| | | } |
| | | super(new ConnectionFactory[] { primary, secondary }, scheduler); |
| | | } |
| | | |
| | | |
| | |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public Connection getConnection() throws DirectoryException |
| | | int getStartIndex() |
| | | { |
| | | if (secondary == null) |
| | | { |
| | | // No fail-over so just use the primary. |
| | | return primary.getConnection(); |
| | | } |
| | | else |
| | | { |
| | | try |
| | | { |
| | | return primary.getConnection(); |
| | | } |
| | | catch (final DirectoryException e) |
| | | { |
| | | return secondary.getConnection(); |
| | | } |
| | | } |
| | | // Always start with the primaries. |
| | | return 0; |
| | | } |
| | | |
| | | } |
| | |
| | | |
| | | /** |
| | | * An interface for obtaining a connection factory for LDAP connections to a |
| | | * named LDAP server. |
| | | * named LDAP server and the monitoring scheduler. |
| | | */ |
| | | static interface LDAPConnectionFactoryProvider |
| | | static interface Provider |
| | | { |
| | | /** |
| | | * Returns a connection factory which can be used for obtaining connections |
| | |
| | | */ |
| | | ConnectionFactory getLDAPConnectionFactory(String host, int port, |
| | | LDAPPassThroughAuthenticationPolicyCfg cfg); |
| | | |
| | | |
| | | |
| | | /** |
| | | * Returns the scheduler which should be used to periodically ping |
| | | * connection factories to determine when they are online. |
| | | * |
| | | * @return The scheduler which should be used to periodically ping |
| | | * connection factories to determine when they are online. |
| | | */ |
| | | ScheduledExecutorService getScheduledExecutorService(); |
| | | } |
| | | |
| | | |
| | |
| | | * A simplistic load-balancer connection factory implementation using |
| | | * approximately round-robin balancing. |
| | | */ |
| | | static final class LoadBalancer implements ConnectionFactory, Closeable |
| | | static final class RoundRobinLoadBalancer extends AbstractLoadBalancer |
| | | { |
| | | private final ConnectionFactory[] factories; |
| | | private final AtomicInteger nextIndex = new AtomicInteger(); |
| | | private final int maxIndex; |
| | | |
| | |
| | | * |
| | | * @param factories |
| | | * The list of underlying connection factories. |
| | | * @param scheduler |
| | | * The monitoring scheduler. |
| | | */ |
| | | LoadBalancer(final ConnectionFactory[] factories) |
| | | RoundRobinLoadBalancer(final ConnectionFactory[] factories, |
| | | final ScheduledExecutorService scheduler) |
| | | { |
| | | this.factories = factories; |
| | | super(factories, scheduler); |
| | | this.maxIndex = factories.length; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Close underlying connection pools. |
| | | */ |
| | | @Override |
| | | public void close() |
| | | { |
| | | for (final ConnectionFactory factory : factories) |
| | | { |
| | | factory.close(); |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public Connection getConnection() throws DirectoryException |
| | | { |
| | | final int startIndex = getStartIndex(); |
| | | int index = startIndex; |
| | | for (;;) |
| | | { |
| | | final ConnectionFactory factory = factories[index]; |
| | | |
| | | try |
| | | { |
| | | return factory.getConnection(); |
| | | } |
| | | catch (final DirectoryException e) |
| | | { |
| | | // Try the next index. |
| | | if (++index == maxIndex) |
| | | { |
| | | index = 0; |
| | | } |
| | | |
| | | // If all the factories have been tried then give up and throw the |
| | | // exception. |
| | | if (index == startIndex) |
| | | { |
| | | throw e; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | // Determine the start index. |
| | | private int getStartIndex() |
| | | int getStartIndex() |
| | | { |
| | | // A round robin pool of one connection factories is unlikely in |
| | | // practice and requires special treatment. |
| | |
| | | // Current configuration. |
| | | private LDAPPassThroughAuthenticationPolicyCfg cfg; |
| | | |
| | | private FailoverConnectionFactory searchFactory = null; |
| | | private FailoverConnectionFactory bindFactory = null; |
| | | private ConnectionFactory searchFactory = null; |
| | | private ConnectionFactory bindFactory = null; |
| | | |
| | | |
| | | |
| | |
| | | // authenticated user. |
| | | |
| | | // Create load-balancers for primary servers. |
| | | final LoadBalancer primarySearchLoadBalancer; |
| | | final LoadBalancer primaryBindLoadBalancer; |
| | | final RoundRobinLoadBalancer primarySearchLoadBalancer; |
| | | final RoundRobinLoadBalancer primaryBindLoadBalancer; |
| | | final ScheduledExecutorService scheduler = provider |
| | | .getScheduledExecutorService(); |
| | | |
| | | Set<String> servers = cfg.getPrimaryRemoteLDAPServer(); |
| | | ConnectionPool[] searchPool = new ConnectionPool[servers.size()]; |
| | |
| | | cfg.getMappedSearchBindPassword())); |
| | | bindPool[index++] = new ConnectionPool(factory); |
| | | } |
| | | primarySearchLoadBalancer = new LoadBalancer(searchPool); |
| | | primaryBindLoadBalancer = new LoadBalancer(bindPool); |
| | | primarySearchLoadBalancer = new RoundRobinLoadBalancer(searchPool, |
| | | scheduler); |
| | | primaryBindLoadBalancer = new RoundRobinLoadBalancer(bindPool, scheduler); |
| | | |
| | | // Create load-balancers for secondary servers. |
| | | final LoadBalancer secondarySearchLoadBalancer; |
| | | final LoadBalancer secondaryBindLoadBalancer; |
| | | |
| | | servers = cfg.getSecondaryRemoteLDAPServer(); |
| | | if (servers.isEmpty()) |
| | | { |
| | | secondarySearchLoadBalancer = null; |
| | | secondaryBindLoadBalancer = null; |
| | | searchFactory = primarySearchLoadBalancer; |
| | | bindFactory = primaryBindLoadBalancer; |
| | | } |
| | | else |
| | | { |
| | |
| | | cfg.getMappedSearchBindPassword())); |
| | | bindPool[index++] = new ConnectionPool(factory); |
| | | } |
| | | secondarySearchLoadBalancer = new LoadBalancer(searchPool); |
| | | secondaryBindLoadBalancer = new LoadBalancer(bindPool); |
| | | final RoundRobinLoadBalancer secondarySearchLoadBalancer = |
| | | new RoundRobinLoadBalancer(searchPool, scheduler); |
| | | final RoundRobinLoadBalancer secondaryBindLoadBalancer = |
| | | new RoundRobinLoadBalancer(bindPool, scheduler); |
| | | searchFactory = new FailoverLoadBalancer(primarySearchLoadBalancer, |
| | | secondarySearchLoadBalancer, scheduler); |
| | | bindFactory = new FailoverLoadBalancer(primaryBindLoadBalancer, |
| | | secondaryBindLoadBalancer, scheduler); |
| | | } |
| | | |
| | | searchFactory = new FailoverConnectionFactory(primarySearchLoadBalancer, |
| | | secondarySearchLoadBalancer); |
| | | bindFactory = new FailoverConnectionFactory(primaryBindLoadBalancer, |
| | | secondaryBindLoadBalancer); |
| | | } |
| | | |
| | | |
| | |
| | | } |
| | | |
| | | // The provider which should be used by policies to create LDAP connections. |
| | | private final LDAPConnectionFactoryProvider provider; |
| | | private final Provider provider; |
| | | |
| | | /** |
| | | * The default LDAP connection factory provider. |
| | | */ |
| | | private static final LDAPConnectionFactoryProvider DEFAULT_PROVIDER = |
| | | new LDAPConnectionFactoryProvider() |
| | | private static final Provider DEFAULT_PROVIDER = new Provider() |
| | | { |
| | | |
| | | // Global scheduler used for periodically monitoring connection factories in |
| | | // order to detect when they are online. |
| | | private final ScheduledExecutorService scheduler = Executors |
| | | .newScheduledThreadPool(2, new ThreadFactory() |
| | | { |
| | | |
| | | @Override |
| | | public Thread newThread(final Runnable r) |
| | | { |
| | | final Thread t = new DirectoryThread(r, |
| | | "LDAP PTA connection monitor thread"); |
| | | t.setDaemon(true); |
| | | return t; |
| | | } |
| | | }); |
| | | |
| | | |
| | | |
| | | @Override |
| | | public ConnectionFactory getLDAPConnectionFactory(final String host, |
| | | final int port, final LDAPPassThroughAuthenticationPolicyCfg cfg) |
| | |
| | | return new LDAPConnectionFactory(host, port, cfg); |
| | | } |
| | | |
| | | |
| | | |
| | | @Override |
| | | public ScheduledExecutorService getScheduledExecutorService() |
| | | { |
| | | return scheduler; |
| | | } |
| | | |
| | | }; |
| | | |
| | | |
| | |
| | | * The LDAP connection factory provider implementation which LDAP PTA |
| | | * authentication policies will use. |
| | | */ |
| | | LDAPPassThroughAuthenticationPolicyFactory( |
| | | final LDAPConnectionFactoryProvider provider) |
| | | LDAPPassThroughAuthenticationPolicyFactory(final Provider provider) |
| | | { |
| | | this.provider = provider; |
| | | } |