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