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}