001/* 002 * Copyright 2008-2019 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-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.List; 027import java.util.concurrent.atomic.AtomicLong; 028import javax.net.SocketFactory; 029 030import com.unboundid.util.Debug; 031import com.unboundid.util.ObjectPair; 032import com.unboundid.util.NotMutable; 033import com.unboundid.util.StaticUtils; 034import com.unboundid.util.ThreadSafety; 035import com.unboundid.util.ThreadSafetyLevel; 036import com.unboundid.util.Validator; 037 038 039 040/** 041 * This class provides a server set implementation that will use a round-robin 042 * algorithm to select the server to which the connection should be established. 043 * Any number of servers may be included in this server set, and each request 044 * will attempt to retrieve a connection to the next server in the list, 045 * circling back to the beginning of the list as necessary. If a server is 046 * unavailable when an attempt is made to establish a connection to it, then 047 * the connection will be established to the next available server in the set. 048 * <BR><BR> 049 * This server set implementation has the ability to maintain a temporary 050 * blacklist of servers that have been recently found to be unavailable or 051 * unsuitable for use. If an attempt to establish or authenticate a 052 * connection fails, if post-connect processing fails for that connection, or if 053 * health checking indicates that the connection is not suitable, then that 054 * server may be placed on the blacklist so that it will only be tried as a last 055 * resort after all non-blacklisted servers have been attempted. The blacklist 056 * will be checked at regular intervals to determine whether a server should be 057 * re-instated to availability. 058 * <BR><BR> 059 * <H2>Example</H2> 060 * The following example demonstrates the process for creating a round-robin 061 * server set that may be used to establish connections to either of two 062 * servers. When using the server set to attempt to create a connection, it 063 * will first try one of the servers, but will fail over to the other if the 064 * first one attempted is not available: 065 * <PRE> 066 * // Create arrays with the addresses and ports of the directory server 067 * // instances. 068 * String[] addresses = 069 * { 070 * server1Address, 071 * server2Address 072 * }; 073 * int[] ports = 074 * { 075 * server1Port, 076 * server2Port 077 * }; 078 * 079 * // Create the server set using the address and port arrays. 080 * RoundRobinServerSet roundRobinSet = 081 * new RoundRobinServerSet(addresses, ports); 082 * 083 * // Verify that we can establish a single connection using the server set. 084 * LDAPConnection connection = roundRobinSet.getConnection(); 085 * RootDSE rootDSEFromConnection = connection.getRootDSE(); 086 * connection.close(); 087 * 088 * // Verify that we can establish a connection pool using the server set. 089 * SimpleBindRequest bindRequest = 090 * new SimpleBindRequest("uid=pool.user,dc=example,dc=com", "password"); 091 * LDAPConnectionPool pool = 092 * new LDAPConnectionPool(roundRobinSet, bindRequest, 10); 093 * RootDSE rootDSEFromPool = pool.getRootDSE(); 094 * pool.close(); 095 * </PRE> 096 */ 097@NotMutable() 098@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 099public final class RoundRobinServerSet 100 extends ServerSet 101{ 102 /** 103 * The name of a system property that can be used to override the default 104 * blacklist check interval, in milliseconds. 105 */ 106 static final String PROPERTY_DEFAULT_BLACKLIST_CHECK_INTERVAL_MILLIS = 107 RoundRobinServerSet.class.getName() + 108 ".defaultBlacklistCheckIntervalMillis"; 109 110 111 112 // A counter used to determine the next slot that should be used. 113 private final AtomicLong nextSlot; 114 115 // The bind request to use to authenticate connections created by this 116 // server set. 117 private final BindRequest bindRequest; 118 119 // The port numbers of the target servers. 120 private final int[] ports; 121 122 // The set of connection options to use for new connections. 123 private final LDAPConnectionOptions connectionOptions; 124 125 // The post-connect processor to invoke against connections created by this 126 // server set. 127 private final PostConnectProcessor postConnectProcessor; 128 129 // The blacklist manager for this server set. 130 private final ServerSetBlacklistManager blacklistManager; 131 132 // The socket factory to use to establish connections. 133 private final SocketFactory socketFactory; 134 135 // The addresses of the target servers. 136 private final String[] addresses; 137 138 139 140 /** 141 * Creates a new round robin server set with the specified set of directory 142 * server addresses and port numbers. It will use the default socket factory 143 * provided by the JVM to create the underlying sockets. 144 * 145 * @param addresses The addresses of the directory servers to which the 146 * connections should be established. It must not be 147 * {@code null} or empty. 148 * @param ports The ports of the directory servers to which the 149 * connections should be established. It must not be 150 * {@code null}, and it must have the same number of 151 * elements as the {@code addresses} array. The order of 152 * elements in the {@code addresses} array must correspond 153 * to the order of elements in the {@code ports} array. 154 */ 155 public RoundRobinServerSet(final String[] addresses, final int[] ports) 156 { 157 this(addresses, ports, null, null); 158 } 159 160 161 162 /** 163 * Creates a new round robin server set with the specified set of directory 164 * server addresses and port numbers. It will use the default socket factory 165 * provided by the JVM to create the underlying sockets. 166 * 167 * @param addresses The addresses of the directory servers to which 168 * the connections should be established. It must 169 * not be {@code null} or empty. 170 * @param ports The ports of the directory servers to which the 171 * connections should be established. It must not 172 * be {@code null}, and it must have the same 173 * number of elements as the {@code addresses} 174 * array. The order of elements in the 175 * {@code addresses} array must correspond to the 176 * order of elements in the {@code ports} array. 177 * @param connectionOptions The set of connection options to use for the 178 * underlying connections. 179 */ 180 public RoundRobinServerSet(final String[] addresses, final int[] ports, 181 final LDAPConnectionOptions connectionOptions) 182 { 183 this(addresses, ports, null, connectionOptions); 184 } 185 186 187 188 /** 189 * Creates a new round robin server set with the specified set of directory 190 * server addresses and port numbers. It will use the provided socket factory 191 * to create the underlying sockets. 192 * 193 * @param addresses The addresses of the directory servers to which the 194 * connections should be established. It must not be 195 * {@code null} or empty. 196 * @param ports The ports of the directory servers to which the 197 * connections should be established. It must not be 198 * {@code null}, and it must have the same number of 199 * elements as the {@code addresses} array. The order 200 * of elements in the {@code addresses} array must 201 * correspond to the order of elements in the 202 * {@code ports} array. 203 * @param socketFactory The socket factory to use to create the underlying 204 * connections. 205 */ 206 public RoundRobinServerSet(final String[] addresses, final int[] ports, 207 final SocketFactory socketFactory) 208 { 209 this(addresses, ports, socketFactory, null); 210 } 211 212 213 214 /** 215 * Creates a new round robin server set with the specified set of directory 216 * server addresses and port numbers. It will use the provided socket factory 217 * to create the underlying sockets. 218 * 219 * @param addresses The addresses of the directory servers to which 220 * the connections should be established. It must 221 * not be {@code null} or empty. 222 * @param ports The ports of the directory servers to which the 223 * connections should be established. It must not 224 * be {@code null}, and it must have the same 225 * number of elements as the {@code addresses} 226 * array. The order of elements in the 227 * {@code addresses} array must correspond to the 228 * order of elements in the {@code ports} array. 229 * @param socketFactory The socket factory to use to create the 230 * underlying connections. 231 * @param connectionOptions The set of connection options to use for the 232 * underlying connections. 233 */ 234 public RoundRobinServerSet(final String[] addresses, final int[] ports, 235 final SocketFactory socketFactory, 236 final LDAPConnectionOptions connectionOptions) 237 { 238 this(addresses, ports, socketFactory, connectionOptions, null, null); 239 } 240 241 242 243 /** 244 * Creates a new round robin server set with the specified set of directory 245 * server addresses and port numbers. It will use the provided socket factory 246 * to create the underlying sockets. 247 * 248 * @param addresses The addresses of the directory servers to 249 * which the connections should be established. 250 * It must not be {@code null} or empty. 251 * @param ports The ports of the directory servers to which 252 * the connections should be established. It 253 * must not be {@code null}, and it must have 254 * the same number of elements as the 255 * {@code addresses} array. The order of 256 * elements in the {@code addresses} array must 257 * correspond to the order of elements in the 258 * {@code ports} array. 259 * @param socketFactory The socket factory to use to create the 260 * underlying connections. 261 * @param connectionOptions The set of connection options to use for the 262 * underlying connections. 263 * @param bindRequest The bind request that should be used to 264 * authenticate newly established connections. 265 * It may be {@code null} if this server set 266 * should not perform any authentication. 267 * @param postConnectProcessor The post-connect processor that should be 268 * invoked on newly established connections. It 269 * may be {@code null} if this server set should 270 * not perform any post-connect processing. 271 */ 272 public RoundRobinServerSet(final String[] addresses, final int[] ports, 273 final SocketFactory socketFactory, 274 final LDAPConnectionOptions connectionOptions, 275 final BindRequest bindRequest, 276 final PostConnectProcessor postConnectProcessor) 277 { 278 this(addresses, ports, socketFactory, connectionOptions, bindRequest, 279 postConnectProcessor, getDefaultBlacklistCheckIntervalMillis()); 280 } 281 282 283 284 /** 285 * Creates a new round robin server set with the specified set of directory 286 * server addresses and port numbers. It will use the provided socket factory 287 * to create the underlying sockets. 288 * 289 * @param addresses The addresses of the directory 290 * servers to which the connections 291 * should be established. It must not 292 * be {@code null} or empty. 293 * @param ports The ports of the directory servers to 294 * which the connections should be 295 * established. It must not be 296 * {@code null}, and it must have the 297 * same number of elements as the 298 * {@code addresses} array. The order 299 * of elements in the {@code addresses} 300 * array must correspond to the order of 301 * elements in the {@code ports} array. 302 * @param socketFactory The socket factory to use to create 303 * the underlying connections. 304 * @param connectionOptions The set of connection options to use 305 * for the underlying connections. 306 * @param bindRequest The bind request that should be used 307 * to authenticate newly established 308 * connections. It may be {@code null} 309 * if this server set should not perform 310 * any authentication. 311 * @param postConnectProcessor The post-connect processor that 312 * should be invoked on newly 313 * established connections. It may be 314 * {@code null} if this server set 315 * should not perform any post-connect 316 * processing. 317 * @param blacklistCheckIntervalMillis The length of time in milliseconds 318 * between checks of servers on the 319 * blacklist to determine whether they 320 * are once again suitable for use. A 321 * value that is less than or equal to 322 * zero indicates that no blacklist 323 * should be maintained. 324 */ 325 public RoundRobinServerSet(final String[] addresses, final int[] ports, 326 final SocketFactory socketFactory, 327 final LDAPConnectionOptions connectionOptions, 328 final BindRequest bindRequest, 329 final PostConnectProcessor postConnectProcessor, 330 final long blacklistCheckIntervalMillis) 331 { 332 Validator.ensureNotNull(addresses, ports); 333 Validator.ensureTrue(addresses.length > 0, 334 "RoundRobinServerSet.addresses must not be empty."); 335 Validator.ensureTrue(addresses.length == ports.length, 336 "RoundRobinServerSet addresses and ports arrays must be the same " + 337 "size."); 338 339 this.addresses = addresses; 340 this.ports = ports; 341 this.bindRequest = bindRequest; 342 this.postConnectProcessor = postConnectProcessor; 343 344 if (socketFactory == null) 345 { 346 this.socketFactory = SocketFactory.getDefault(); 347 } 348 else 349 { 350 this.socketFactory = socketFactory; 351 } 352 353 if (connectionOptions == null) 354 { 355 this.connectionOptions = new LDAPConnectionOptions(); 356 } 357 else 358 { 359 this.connectionOptions = connectionOptions; 360 } 361 362 nextSlot = new AtomicLong(0L); 363 364 if (blacklistCheckIntervalMillis > 0L) 365 { 366 blacklistManager = new ServerSetBlacklistManager(this, socketFactory, 367 connectionOptions, bindRequest, postConnectProcessor, 368 blacklistCheckIntervalMillis); 369 } 370 else 371 { 372 blacklistManager = null; 373 } 374 } 375 376 377 378 /** 379 * Retrieves the default blacklist check interval (in milliseconds that should 380 * be used if it is not specified. 381 * 382 * @return The default blacklist check interval (in milliseconds that should 383 * be used if it is not specified. 384 */ 385 private static long getDefaultBlacklistCheckIntervalMillis() 386 { 387 final String propertyValue = StaticUtils.getSystemProperty( 388 PROPERTY_DEFAULT_BLACKLIST_CHECK_INTERVAL_MILLIS); 389 if (propertyValue != null) 390 { 391 try 392 { 393 return Long.parseLong(propertyValue); 394 } 395 catch (final Exception e) 396 { 397 Debug.debugException(e); 398 } 399 } 400 401 return 30_000L; 402 } 403 404 405 406 /** 407 * Retrieves the addresses of the directory servers to which the connections 408 * should be established. 409 * 410 * @return The addresses of the directory servers to which the connections 411 * should be established. 412 */ 413 public String[] getAddresses() 414 { 415 return addresses; 416 } 417 418 419 420 /** 421 * Retrieves the ports of the directory servers to which the connections 422 * should be established. 423 * 424 * @return The ports of the directory servers to which the connections should 425 * be established. 426 */ 427 public int[] getPorts() 428 { 429 return ports; 430 } 431 432 433 434 /** 435 * Retrieves the socket factory that will be used to establish connections. 436 * 437 * @return The socket factory that will be used to establish connections. 438 */ 439 public SocketFactory getSocketFactory() 440 { 441 return socketFactory; 442 } 443 444 445 446 /** 447 * Retrieves the set of connection options that will be used for underlying 448 * connections. 449 * 450 * @return The set of connection options that will be used for underlying 451 * connections. 452 */ 453 public LDAPConnectionOptions getConnectionOptions() 454 { 455 return connectionOptions; 456 } 457 458 459 460 /** 461 * {@inheritDoc} 462 */ 463 @Override() 464 public boolean includesAuthentication() 465 { 466 return (bindRequest != null); 467 } 468 469 470 471 /** 472 * {@inheritDoc} 473 */ 474 @Override() 475 public boolean includesPostConnectProcessing() 476 { 477 return (postConnectProcessor != null); 478 } 479 480 481 482 /** 483 * {@inheritDoc} 484 */ 485 @Override() 486 public LDAPConnection getConnection() 487 throws LDAPException 488 { 489 return getConnection(null); 490 } 491 492 493 494 /** 495 * {@inheritDoc} 496 */ 497 @Override() 498 public LDAPConnection getConnection( 499 final LDAPConnectionPoolHealthCheck healthCheck) 500 throws LDAPException 501 { 502 final int initialSlotNumber = 503 (int) (nextSlot.getAndIncrement() % addresses.length); 504 505 LDAPException lastException = null; 506 List<ObjectPair<String,Integer>> blacklistedServers = null; 507 for (int i=0; i < addresses.length; i++) 508 { 509 final int slotNumber = ((initialSlotNumber + i) % addresses.length); 510 final String address = addresses[slotNumber]; 511 final int port = ports[slotNumber]; 512 if ((blacklistManager != null) && 513 blacklistManager.isBlacklisted(address, port)) 514 { 515 if (blacklistedServers == null) 516 { 517 blacklistedServers = new ArrayList<>(addresses.length); 518 } 519 520 blacklistedServers.add(new ObjectPair<>(address, port)); 521 continue; 522 } 523 524 try 525 { 526 final LDAPConnection c = new LDAPConnection(socketFactory, 527 connectionOptions, addresses[slotNumber], ports[slotNumber]); 528 doBindPostConnectAndHealthCheckProcessing(c, bindRequest, 529 postConnectProcessor, healthCheck); 530 associateConnectionWithThisServerSet(c); 531 return c; 532 } 533 catch (final LDAPException e) 534 { 535 Debug.debugException(e); 536 lastException = e; 537 if (blacklistManager != null) 538 { 539 blacklistManager.addToBlacklist(address, port, healthCheck); 540 } 541 } 542 } 543 544 545 // If we've gotten here, then we couldn't get a connection from a 546 // non-blacklisted server. If there were any blacklisted servers, then try 547 // them as a last resort. 548 if (blacklistedServers != null) 549 { 550 for (final ObjectPair<String,Integer> hostPort : blacklistedServers) 551 { 552 try 553 { 554 final LDAPConnection c = new LDAPConnection(socketFactory, 555 connectionOptions, hostPort.getFirst(), hostPort.getSecond()); 556 doBindPostConnectAndHealthCheckProcessing(c, bindRequest, 557 postConnectProcessor, healthCheck); 558 associateConnectionWithThisServerSet(c); 559 blacklistManager.removeFromBlacklist(hostPort); 560 return c; 561 } 562 catch (final LDAPException e) 563 { 564 Debug.debugException(e); 565 lastException = e; 566 } 567 } 568 } 569 570 571 // If we've gotten here, then we've failed to connect to any of the servers, 572 // so propagate the last exception to the caller. 573 throw lastException; 574 } 575 576 577 578 /** 579 * Retrieves the blacklist manager for this server set. 580 * 581 * @return The blacklist manager for this server set, or {@code null} if no 582 * blacklist will be maintained. 583 */ 584 ServerSetBlacklistManager getBlacklistManager() 585 { 586 return blacklistManager; 587 } 588 589 590 591 /** 592 * {@inheritDoc} 593 */ 594 @Override() 595 public void toString(final StringBuilder buffer) 596 { 597 buffer.append("RoundRobinServerSet(servers={"); 598 599 for (int i=0; i < addresses.length; i++) 600 { 601 if (i > 0) 602 { 603 buffer.append(", "); 604 } 605 606 buffer.append(addresses[i]); 607 buffer.append(':'); 608 buffer.append(ports[i]); 609 } 610 611 buffer.append("}, includesAuthentication="); 612 buffer.append(bindRequest != null); 613 buffer.append(", includesPostConnectProcessing="); 614 buffer.append(postConnectProcessor != null); 615 buffer.append(')'); 616 } 617}