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}