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