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