Multiple LDAP Servers

Nov 28, 2012 at 10:29 PM
Edited Nov 29, 2012 at 7:10 PM

We have multiple LDAP servers that are mirrored.  Is there a way to get the connection factories to serve connections to multiple servers in a balanced way?

Coordinator
Nov 29, 2012 at 6:34 PM

Are you using Active Directory or another server technology?  Most likely you'll have to roll your own connection factory by implementing IConnectionFactory and associating it with your LdapConfiguration.

Nov 29, 2012 at 7:11 PM

We're using an ldap server, it's not AD.  I have a work around in place just using multiple configuration objects, it would just be easier if the configuration object could handle multiple servers for me.  I'll look into creating my own IConnectionFactory.  Thanks,

Coordinator
Dec 2, 2012 at 12:53 AM

Aaron,

I took a stab at a simple implementation.  I've only done some minor testing so use with caution. 

Things that are not accounted for

  • Removing a server that has in use connections
  • Handling servers that go down and automatically taking them offline
  • Thread safety for connection counts.

Using the code:

var config = new LdapConfiguration();

var factory = new PooledServerConnectionFactory();
factory.AddServer(new LdapDirectoryIdentifier("localhost", 389), 1);
factory.AddServer(new LdapDirectoryIdentifier("computername", 389), 1);

config.ConfigureCustomFactory(factory);

Code:

public class PooledServerConnectionFactory : ILdapConnectionFactory
{
    private readonly ConcurrentDictionary<string, ServerPoolMemberConnectionFactory> _servers;

    public PooledServerConnectionFactory()
    {
        _servers = new ConcurrentDictionary<string, ServerPoolMemberConnectionFactory>(StringComparer.OrdinalIgnoreCase);
    }

    public void AddServer(LdapDirectoryIdentifier identifier, int maxConnections, int protocolVersion = 3, bool ssl = false, double? timeout = null, NetworkCredential credentials = null, AuthType? authType = null)
    {
        var serverName = identifier.Servers[0];
        var factory = new LdapConnectionFactory(serverName);
        if (credentials != null)
            factory.AuthenticateAs(credentials);
        if (authType.HasValue)
            factory.AuthenticateBy(authType.Value);

        if (timeout.HasValue)
            factory.ConnectionTimeoutIn(timeout.Value);

        factory.ProtocolVersion(protocolVersion);

        if (identifier.FullyQualifiedDnsHostName)
            factory.ServerNameIsFullyQualified();

        if (identifier.Connectionless)
            factory.UseUdp();

        if (ssl) factory.UseSsl();

        factory.UsePort(identifier.PortNumber);

        _servers[serverName] = new ServerPoolMemberConnectionFactory(serverName, factory, maxConnections); 
    }

    public void RemoveServer(string serverName)
    {
        ServerPoolMemberConnectionFactory factory;
        if (!_servers.TryRemove(serverName, out factory))
        {
            throw new InvalidOperationException(serverName + " not found.");
        }
    }

    public LdapConnection GetConnection()
    {
        var factory = _servers.FirstOrDefault(c => c.Value.CanConnect);

        if (Equals(factory, default(KeyValuePair<string, ServerPoolMemberConnectionFactory>))) 
            throw new InvalidOperationException("No connections available");

        return factory.Value.GetConnection();
    }

    public void ReleaseConnection(LdapConnection connection)
    {
        var server = ((LdapDirectoryIdentifier)connection.Directory).Servers[0];

        _servers[server].ReleaseConnection(connection);
    }

    private class ServerPoolMemberConnectionFactory : ILdapConnectionFactory
    {
        private ILdapConnectionFactory _factory;
        private int _maxConnections;
        private int _connectionCount;
        private string _serverName;

        public ServerPoolMemberConnectionFactory(string serverName, ILdapConnectionFactory factory, int maxConnections)
        {
            _serverName = serverName;
            _factory = factory;
            _maxConnections = maxConnections;
        }

        public bool CanConnect { get { return _maxConnections > _connectionCount; } }

        public LdapConnection GetConnection()
        {
            if (!CanConnect) throw new InvalidOperationException("Too many connections for " + _serverName);
                
            var connection = _factory.GetConnection();
            _connectionCount++;

            return connection;
        }

        public void ReleaseConnection(LdapConnection connection)
        {
            _factory.ReleaseConnection(connection);
            _connectionCount--;
        }
    }
}