001/*
002 * Copyright 2013-2019 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2013-2019 Ping Identity Corporation
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021package com.unboundid.ldap.sdk;
022
023
024
025import java.util.ArrayList;
026import java.util.Collections;
027import java.util.Iterator;
028import java.util.LinkedHashMap;
029import java.util.List;
030import java.util.Map;
031import java.util.TreeMap;
032import java.util.concurrent.atomic.AtomicLong;
033import javax.net.SocketFactory;
034
035import com.unboundid.util.Debug;
036import com.unboundid.util.NotMutable;
037import com.unboundid.util.ObjectPair;
038import com.unboundid.util.StaticUtils;
039import com.unboundid.util.ThreadSafety;
040import com.unboundid.util.ThreadSafetyLevel;
041import com.unboundid.util.Validator;
042
043
044
045/**
046 * This class provides a server set implementation that will establish a
047 * connection to the server with the fewest established connections previously
048 * created by the same server set instance.  If there are multiple servers that
049 * share the fewest number of established connections, the first one in the list
050 * will be chosen.  If a server is unavailable when an attempt is made to
051 * establish a connection to it, then the connection will be established to the
052 * available server with the next fewest number of established connections.
053 * <BR><BR>
054 * This server set implementation has the ability to maintain a temporary
055 * blacklist of servers that have been recently found to be unavailable or
056 * unsuitable for use.  If an attempt to establish or authenticate a
057 * connection fails, if post-connect processing fails for that connection, or if
058 * health checking indicates that the connection is not suitable, then that
059 * server may be placed on the blacklist so that it will only be tried as a last
060 * resort after all non-blacklisted servers have been attempted.  The blacklist
061 * will be checked at regular intervals to determine whether a server should be
062 * re-instated to availability.
063 * <BR><BR>
064 * Note that this server set implementation is primarily intended for use with
065 * connection pools, but is also suitable for cases in which standalone
066 * connections are created as long as there will not be any attempt to close the
067 * connections when they are re-established.  It is not suitable for use in
068 * connections that may be re-established one or more times after being closed.
069 * <BR><BR>
070 * <H2>Example</H2>
071 * The following example demonstrates the process for creating a fewest
072 * connections server set that may be used to establish connections to either of
073 * two servers.
074 * <PRE>
075 * // Create arrays with the addresses and ports of the directory server
076 * // instances.
077 * String[] addresses =
078 * {
079 *   server1Address,
080 *   server2Address
081 * };
082 * int[] ports =
083 * {
084 *   server1Port,
085 *   server2Port
086 * };
087 *
088 * // Create the server set using the address and port arrays.
089 * FewestConnectionsServerSet fewestConnectionsSet =
090 *      new FewestConnectionsServerSet(addresses, ports);
091 *
092 * // Verify that we can establish a single connection using the server set.
093 * LDAPConnection connection = fewestConnectionsSet.getConnection();
094 * RootDSE rootDSEFromConnection = connection.getRootDSE();
095 * connection.close();
096 *
097 * // Verify that we can establish a connection pool using the server set.
098 * SimpleBindRequest bindRequest =
099 *      new SimpleBindRequest("uid=pool.user,dc=example,dc=com", "password");
100 * LDAPConnectionPool pool =
101 *      new LDAPConnectionPool(fewestConnectionsSet, bindRequest, 10);
102 * RootDSE rootDSEFromPool = pool.getRootDSE();
103 * pool.close();
104 * </PRE>
105 */
106@NotMutable()
107@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
108public final class FewestConnectionsServerSet
109       extends ServerSet
110{
111  /**
112   * The name of a system property that can be used to override the default
113   * blacklist check interval, in milliseconds.
114   */
115  static final String PROPERTY_DEFAULT_BLACKLIST_CHECK_INTERVAL_MILLIS =
116       FewestConnectionsServerSet.class.getName() +
117            ".defaultBlacklistCheckIntervalMillis";
118
119
120
121  // The bind request to use to authenticate connections created by this
122  // server set.
123  private final BindRequest bindRequest;
124
125  // The set of connection options to use for new connections.
126  private final LDAPConnectionOptions connectionOptions;
127
128  // A map with the number of connections currently established for each server.
129  private final Map<ObjectPair<String,Integer>,AtomicLong>
130       connectionCountsByServer;
131
132  // The post-connect processor to invoke against connections created by this
133  // server set.
134  private final PostConnectProcessor postConnectProcessor;
135
136  // The blacklist manager for this server set.
137  private final ServerSetBlacklistManager blacklistManager;
138
139  // The socket factory to use to establish connections.
140  private final SocketFactory socketFactory;
141
142
143
144  /**
145   * Creates a new fewest connections server set with the specified set of
146   * directory server addresses and port numbers.  It will use the default
147   * socket factory provided by the JVM to create the underlying sockets.
148   *
149   * @param  addresses  The addresses of the directory servers to which the
150   *                    connections should be established.  It must not be
151   *                    {@code null} or empty.
152   * @param  ports      The ports of the directory servers to which the
153   *                    connections should be established.  It must not be
154   *                    {@code null}, and it must have the same number of
155   *                    elements as the {@code addresses} array.  The order of
156   *                    elements in the {@code addresses} array must correspond
157   *                    to the order of elements in the {@code ports} array.
158   */
159  public FewestConnectionsServerSet(final String[] addresses, final int[] ports)
160  {
161    this(addresses, ports, null, null);
162  }
163
164
165
166  /**
167   * Creates a new fewest connections server set with the specified set of
168   * directory server addresses and port numbers.  It will use the default
169   * socket factory provided by the JVM to create the underlying sockets.
170   *
171   * @param  addresses          The addresses of the directory servers to which
172   *                            the connections should be established.  It must
173   *                            not be {@code null} or empty.
174   * @param  ports              The ports of the directory servers to which the
175   *                            connections should be established.  It must not
176   *                            be {@code null}, and it must have the same
177   *                            number of elements as the {@code addresses}
178   *                            array.  The order of elements in the
179   *                            {@code addresses} array must correspond to the
180   *                            order of elements in the {@code ports} array.
181   * @param  connectionOptions  The set of connection options to use for the
182   *                            underlying connections.
183   */
184  public FewestConnectionsServerSet(final String[] addresses, final int[] ports,
185              final LDAPConnectionOptions connectionOptions)
186  {
187    this(addresses, ports, null, connectionOptions);
188  }
189
190
191
192  /**
193   * Creates a new fewest connections server set with the specified set of
194   * directory server addresses and port numbers.  It will use the provided
195   * socket factory to create the underlying sockets.
196   *
197   * @param  addresses      The addresses of the directory servers to which the
198   *                        connections should be established.  It must not be
199   *                        {@code null} or empty.
200   * @param  ports          The ports of the directory servers to which the
201   *                        connections should be established.  It must not be
202   *                        {@code null}, and it must have the same number of
203   *                        elements as the {@code addresses} array.  The order
204   *                        of elements in the {@code addresses} array must
205   *                        correspond to the order of elements in the
206   *                        {@code ports} array.
207   * @param  socketFactory  The socket factory to use to create the underlying
208   *                        connections.
209   */
210  public FewestConnectionsServerSet(final String[] addresses, final int[] ports,
211                                    final SocketFactory socketFactory)
212  {
213    this(addresses, ports, socketFactory, null);
214  }
215
216
217
218  /**
219   * Creates a new fewest connections server set with the specified set of
220   * directory server addresses and port numbers.  It will use the provided
221   * socket factory to create the underlying sockets.
222   *
223   * @param  addresses          The addresses of the directory servers to which
224   *                            the connections should be established.  It must
225   *                            not be {@code null} or empty.
226   * @param  ports              The ports of the directory servers to which the
227   *                            connections should be established.  It must not
228   *                            be {@code null}, and it must have the same
229   *                            number of elements as the {@code addresses}
230   *                            array.  The order of elements in the
231   *                            {@code addresses} array must correspond to the
232   *                            order of elements in the {@code ports} array.
233   * @param  socketFactory      The socket factory to use to create the
234   *                            underlying connections.
235   * @param  connectionOptions  The set of connection options to use for the
236   *                            underlying connections.
237   */
238  public FewestConnectionsServerSet(final String[] addresses, final int[] ports,
239              final SocketFactory socketFactory,
240              final LDAPConnectionOptions connectionOptions)
241  {
242    this(addresses, ports, socketFactory, connectionOptions, null, null);
243  }
244
245
246
247  /**
248   * Creates a new fewest connections server set with the specified set of
249   * directory server addresses and port numbers.  It will use the provided
250   * socket factory to create the underlying sockets.
251   *
252   * @param  addresses             The addresses of the directory servers to
253   *                               which the connections should be established.
254   *                               It must not be {@code null} or empty.
255   * @param  ports                 The ports of the directory servers to which
256   *                               the connections should be established.  It
257   *                               must not be {@code null}, and it must have
258   *                               the same number of elements as the
259   *                               {@code addresses} array.  The order of
260   *                               elements in the {@code addresses} array must
261   *                               correspond to the order of elements in the
262   *                               {@code ports} array.
263   * @param  socketFactory         The socket factory to use to create the
264   *                               underlying connections.
265   * @param  connectionOptions     The set of connection options to use for the
266   *                               underlying connections.
267   * @param  bindRequest           The bind request that should be used to
268   *                               authenticate newly established connections.
269   *                               It may be {@code null} if this server set
270   *                               should not perform any authentication.
271   * @param  postConnectProcessor  The post-connect processor that should be
272   *                               invoked on newly established connections.  It
273   *                               may be {@code null} if this server set should
274   *                               not perform any post-connect processing.
275   */
276  public FewestConnectionsServerSet(final String[] addresses, final int[] ports,
277              final SocketFactory socketFactory,
278              final LDAPConnectionOptions connectionOptions,
279              final BindRequest bindRequest,
280              final PostConnectProcessor postConnectProcessor)
281  {
282    this(addresses, ports, socketFactory, connectionOptions, bindRequest,
283         postConnectProcessor, getDefaultBlacklistCheckIntervalMillis());
284  }
285
286
287
288  /**
289   * Creates a new fewest connections server set with the specified set of
290   * directory server addresses and port numbers.  It will use the provided
291   * socket factory to create the underlying sockets.
292   *
293   * @param  addresses                     The addresses of the directory
294   *                                       servers to which the connections
295   *                                       should be established.  It must not
296   *                                       be {@code null} or empty.
297   * @param  ports                         The ports of the directory servers to
298   *                                       which the connections should be
299   *                                       established.  It must not be
300   *                                       {@code null}, and it must have the
301   *                                       same number of elements as the
302   *                                       {@code addresses} array.  The order
303   *                                       of elements in the {@code addresses}
304   *                                       array must correspond to the order of
305   *                                       elements in the {@code ports} array.
306   * @param  socketFactory                 The socket factory to use to create
307   *                                       the underlying connections.
308   * @param  connectionOptions             The set of connection options to use
309   *                                       for the underlying connections.
310   * @param  bindRequest                   The bind request that should be used
311   *                                       to authenticate newly established
312   *                                       connections. It may be {@code null}
313   *                                       if this server set should not perform
314   *                                       any authentication.
315   * @param  postConnectProcessor          The post-connect processor that
316   *                                       should be invoked on newly
317   *                                       established connections.  It may be
318   *                                       {@code null} if this server set
319   *                                       should not perform any post-connect
320   *                                       processing.
321   * @param  blacklistCheckIntervalMillis  The length of time in milliseconds
322   *                                       between checks of servers on the
323   *                                       blacklist to determine whether they
324   *                                       are once again suitable for use.  A
325   *                                       value that is less than or equal to
326   *                                       zero indicates that no blacklist
327   *                                       should be maintained.
328   */
329  public FewestConnectionsServerSet(final String[] addresses, final int[] ports,
330              final SocketFactory socketFactory,
331              final LDAPConnectionOptions connectionOptions,
332              final BindRequest bindRequest,
333              final PostConnectProcessor postConnectProcessor,
334              final long blacklistCheckIntervalMillis)
335  {
336    Validator.ensureNotNull(addresses, ports);
337    Validator.ensureTrue(addresses.length > 0,
338         "FewestConnectionsServerSet.addresses must not be empty.");
339    Validator.ensureTrue(addresses.length == ports.length,
340         "FewestConnectionsServerSet addresses and ports arrays must be " +
341              "the same size.");
342
343    final LinkedHashMap<ObjectPair<String,Integer>,AtomicLong> m =
344         new LinkedHashMap<>(StaticUtils.computeMapCapacity(ports.length));
345    for (int i=0; i < addresses.length; i++)
346    {
347      m.put(new ObjectPair<>(addresses[i], ports[i]), new AtomicLong(0L));
348    }
349
350    connectionCountsByServer = Collections.unmodifiableMap(m);
351
352    this.bindRequest = bindRequest;
353    this.postConnectProcessor = postConnectProcessor;
354
355    if (socketFactory == null)
356    {
357      this.socketFactory = SocketFactory.getDefault();
358    }
359    else
360    {
361      this.socketFactory = socketFactory;
362    }
363
364    if (connectionOptions == null)
365    {
366      this.connectionOptions = new LDAPConnectionOptions();
367    }
368    else
369    {
370      this.connectionOptions = connectionOptions;
371    }
372
373    if (blacklistCheckIntervalMillis > 0L)
374    {
375      blacklistManager = new ServerSetBlacklistManager(this, socketFactory,
376           connectionOptions, bindRequest, postConnectProcessor,
377           blacklistCheckIntervalMillis);
378    }
379    else
380    {
381      blacklistManager = null;
382    }
383  }
384
385
386
387  /**
388   * Retrieves the default blacklist check interval (in milliseconds that should
389   * be used if it is not specified.
390   *
391   * @return  The default blacklist check interval (in milliseconds that should
392   *          be used if it is not specified.
393   */
394  private static long getDefaultBlacklistCheckIntervalMillis()
395  {
396    final String propertyValue = StaticUtils.getSystemProperty(
397         PROPERTY_DEFAULT_BLACKLIST_CHECK_INTERVAL_MILLIS);
398    if (propertyValue != null)
399    {
400      try
401      {
402        return Long.parseLong(propertyValue);
403      }
404      catch (final Exception e)
405      {
406        Debug.debugException(e);
407      }
408    }
409
410    return 30_000L;
411  }
412
413
414
415  /**
416   * Retrieves the addresses of the directory servers to which the connections
417   * should be established.
418   *
419   * @return  The addresses of the directory servers to which the connections
420   *          should be established.
421   */
422  public String[] getAddresses()
423  {
424    int i = 0;
425    final String[] addresses = new String[connectionCountsByServer.size()];
426    for (final ObjectPair<String,Integer> hostPort :
427         connectionCountsByServer.keySet())
428    {
429      addresses[i++] = hostPort.getFirst();
430    }
431
432    return addresses;
433  }
434
435
436
437  /**
438   * Retrieves the ports of the directory servers to which the connections
439   * should be established.
440   *
441   * @return  The ports of the directory servers to which the connections should
442   *          be established.
443   */
444  public int[] getPorts()
445  {
446    int i = 0;
447    final int[] ports = new int[connectionCountsByServer.size()];
448    for (final ObjectPair<String,Integer> hostPort :
449         connectionCountsByServer.keySet())
450    {
451      ports[i++] = hostPort.getSecond();
452    }
453
454    return ports;
455  }
456
457
458
459  /**
460   * Retrieves the socket factory that will be used to establish connections.
461   *
462   * @return  The socket factory that will be used to establish connections.
463   */
464  public SocketFactory getSocketFactory()
465  {
466    return socketFactory;
467  }
468
469
470
471  /**
472   * Retrieves the set of connection options that will be used for underlying
473   * connections.
474   *
475   * @return  The set of connection options that will be used for underlying
476   *          connections.
477   */
478  public LDAPConnectionOptions getConnectionOptions()
479  {
480    return connectionOptions;
481  }
482
483
484
485  /**
486   * {@inheritDoc}
487   */
488  @Override()
489  public boolean includesAuthentication()
490  {
491    return (bindRequest != null);
492  }
493
494
495
496  /**
497   * {@inheritDoc}
498   */
499  @Override()
500  public boolean includesPostConnectProcessing()
501  {
502    return (postConnectProcessor != null);
503  }
504
505
506
507  /**
508   * {@inheritDoc}
509   */
510  @Override()
511  public LDAPConnection getConnection()
512         throws LDAPException
513  {
514    return getConnection(null);
515  }
516
517
518
519  /**
520   * {@inheritDoc}
521   */
522  @Override()
523  public LDAPConnection getConnection(
524                             final LDAPConnectionPoolHealthCheck healthCheck)
525         throws LDAPException
526  {
527    // Organize the servers int lists by increasing numbers of connections.
528    final TreeMap<Long,List<ObjectPair<String,Integer>>> serversByCount =
529         new TreeMap<>();
530    for (final Map.Entry<ObjectPair<String,Integer>,AtomicLong> e :
531        connectionCountsByServer.entrySet())
532    {
533      final ObjectPair<String,Integer> hostPort = e.getKey();
534      final long count = e.getValue().get();
535
536      List<ObjectPair<String,Integer>> l = serversByCount.get(count);
537      if (l == null)
538      {
539        l = new ArrayList<>(connectionCountsByServer.size());
540        serversByCount.put(count, l);
541      }
542      l.add(hostPort);
543    }
544
545
546    // Try the servers in order of fewest connections to most.  If there are
547    // multiple servers with the same number of connections, then randomize the
548    // order of servers in that list to better spread the load across all of
549    // the servers.
550    LDAPException lastException = null;
551    List<ObjectPair<String,Integer>> blacklistedServers = null;
552    for (final List<ObjectPair<String,Integer>> l : serversByCount.values())
553    {
554      if (l.size() > 1)
555      {
556        Collections.shuffle(l);
557      }
558
559      for (final ObjectPair<String,Integer> hostPort : l)
560      {
561        if ((blacklistManager != null) &&
562             blacklistManager.isBlacklisted(hostPort))
563        {
564          if (blacklistedServers == null)
565          {
566            blacklistedServers =
567                 new ArrayList<>(connectionCountsByServer.size());
568          }
569          blacklistedServers.add(hostPort);
570          continue;
571        }
572
573        try
574        {
575          final LDAPConnection conn = new LDAPConnection(socketFactory,
576               connectionOptions, hostPort.getFirst(), hostPort.getSecond());
577          doBindPostConnectAndHealthCheckProcessing(conn, bindRequest,
578               postConnectProcessor, healthCheck);
579          connectionCountsByServer.get(hostPort).incrementAndGet();
580          associateConnectionWithThisServerSet(conn);
581          return conn;
582        }
583        catch (final LDAPException le)
584        {
585          Debug.debugException(le);
586          lastException = le;
587          if (blacklistManager != null)
588          {
589            blacklistManager.addToBlacklist(hostPort, healthCheck);
590          }
591        }
592      }
593    }
594
595
596    // If we've gotten here, then we couldn't get a connection from a
597    // non-blacklisted server.  If there were any blacklisted servers, then try
598    // them as a last resort.
599    if (blacklistedServers != null)
600    {
601      for (final ObjectPair<String,Integer> hostPort : blacklistedServers)
602      {
603        try
604        {
605          final LDAPConnection c = new LDAPConnection(socketFactory,
606               connectionOptions, hostPort.getFirst(), hostPort.getSecond());
607          doBindPostConnectAndHealthCheckProcessing(c, bindRequest,
608               postConnectProcessor, healthCheck);
609          associateConnectionWithThisServerSet(c);
610          blacklistManager.removeFromBlacklist(hostPort);
611          return c;
612        }
613        catch (final LDAPException e)
614        {
615          Debug.debugException(e);
616          lastException = e;
617        }
618      }
619    }
620
621
622    // If we've gotten here, then we've tried all servers without any success,
623    // so throw the last exception that was encountered.
624    throw lastException;
625  }
626
627
628
629  /**
630   * {@inheritDoc}
631   */
632  @Override()
633  protected void handleConnectionClosed(final LDAPConnection connection,
634                                        final String host, final int port,
635                                        final DisconnectType disconnectType,
636                                        final String message,
637                                        final Throwable cause)
638  {
639    final ObjectPair<String,Integer> hostPort = new ObjectPair<>(host, port);
640    final AtomicLong counter = connectionCountsByServer.get(hostPort);
641    if (counter != null)
642    {
643      final long remainingCount = counter.decrementAndGet();
644      if (remainingCount < 0L)
645      {
646        // This shouldn't happen.  If it does, reset it back to zero.
647        counter.compareAndSet(remainingCount, 0L);
648      }
649    }
650  }
651
652
653
654  /**
655   * Retrieves the blacklist manager for this server set.
656   *
657   * @return  The blacklist manager for this server set, or {@code null} if no
658   *          blacklist will be maintained.
659   */
660  ServerSetBlacklistManager getBlacklistManager()
661  {
662    return blacklistManager;
663  }
664
665
666
667  /**
668   * {@inheritDoc}
669   */
670  @Override()
671  public void toString(final StringBuilder buffer)
672  {
673    buffer.append("FewestConnectionsServerSet(servers={");
674
675    final Iterator<Map.Entry<ObjectPair<String,Integer>,AtomicLong>>
676         cbsIterator = connectionCountsByServer.entrySet().iterator();
677    while (cbsIterator.hasNext())
678    {
679      final Map.Entry<ObjectPair<String,Integer>,AtomicLong> e =
680           cbsIterator.next();
681      final ObjectPair<String,Integer> hostPort = e.getKey();
682      final long count = e.getValue().get();
683
684      buffer.append('\'');
685      buffer.append(hostPort.getFirst());
686      buffer.append(':');
687      buffer.append(hostPort.getSecond());
688      buffer.append("':");
689      buffer.append(count);
690
691      if (cbsIterator.hasNext())
692      {
693        buffer.append(", ");
694      }
695    }
696
697    buffer.append("}, includesAuthentication=");
698    buffer.append(bindRequest != null);
699    buffer.append(", includesPostConnectProcessing=");
700    buffer.append(postConnectProcessor != null);
701    buffer.append(')');
702  }
703}