001/* 002 * Copyright 2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2024 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) 2024 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.unboundidds; 037 038 039 040import java.io.Serializable; 041 042import com.unboundid.ldap.sdk.BindResult; 043import com.unboundid.ldap.sdk.DereferencePolicy; 044import com.unboundid.ldap.sdk.DisconnectType; 045import com.unboundid.ldap.sdk.Filter; 046import com.unboundid.ldap.sdk.LDAPConnection; 047import com.unboundid.ldap.sdk.LDAPConnectionPoolHealthCheck; 048import com.unboundid.ldap.sdk.LDAPException; 049import com.unboundid.ldap.sdk.ResultCode; 050import com.unboundid.ldap.sdk.SearchRequest; 051import com.unboundid.ldap.sdk.SearchResultEntry; 052import com.unboundid.ldap.sdk.SearchScope; 053import com.unboundid.util.Debug; 054import com.unboundid.util.NotMutable; 055import com.unboundid.util.NotNull; 056import com.unboundid.util.StaticUtils; 057import com.unboundid.util.ThreadSafety; 058import com.unboundid.util.ThreadSafetyLevel; 059 060import static com.unboundid.ldap.sdk.unboundidds.UnboundIDDSMessages.*; 061 062 063 064/** 065 * This class provides an LDAP connection pool health check implementation that 066 * can determine whether a Ping Identity Directory Server instance is currently 067 * in lockdown mode. Any server found to be in lockdown mode will be considered 068 * unavailable. 069 * <BR> 070 * <BLOCKQUOTE> 071 * <B>NOTE:</B> This class, and other classes within the 072 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 073 * supported for use against Ping Identity, UnboundID, and 074 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 075 * for proprietary functionality or for external specifications that are not 076 * considered stable or mature enough to be guaranteed to work in an 077 * interoperable way with other types of LDAP servers. 078 * </BLOCKQUOTE> 079 */ 080@NotMutable() 081@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 082public final class LockdownModeLDAPConnectionPoolHealthCheck 083 extends LDAPConnectionPoolHealthCheck 084 implements Serializable 085{ 086 /** 087 * The default maximum response time value in milliseconds, which is set to 088 * 5,000 milliseconds or 5 seconds. 089 */ 090 private static final long DEFAULT_MAX_RESPONSE_TIME_MILLIS = 5_000L; 091 092 093 094 /** 095 * The name of the attribute in the status health summary monitor entry that 096 * will be used to determine whether the server is in lockdown mode. 097 */ 098 @NotNull() 099 private static final String IS_IN_LOCKDOWN_MODE_ATTRIBUTE_NAME = 100 "is-in-lockdown-mode"; 101 102 103 104 /** 105 * The DN of the status health summary monitor entry that will be examined. 106 */ 107 @NotNull() 108 private static final String STATUS_HEALTH_SUMMARY_MONITOR_ENTRY_DN = 109 "cn=Status Health Summary,cn=monitor"; 110 111 112 113 /** 114 * The serial version UID for this serializable class. 115 */ 116 private static final long serialVersionUID = 11911667291461474L; 117 118 119 120 // Indicates whether to invoke the test after a connection has been 121 // authenticated. 122 private final boolean invokeAfterAuthentication; 123 124 // Indicates whether to invoke the test during background health checks. 125 private final boolean invokeForBackgroundChecks; 126 127 // Indicates whether to invoke the test when checking out a connection. 128 private final boolean invokeOnCheckout; 129 130 // Indicates whether to invoke the test when creating a new connection. 131 private final boolean invokeOnCreate; 132 133 // Indicates whether to invoke the test whenever an exception is encountered 134 // when using the connection. 135 private final boolean invokeOnException; 136 137 // Indicates whether to invoke the test when releasing a connection. 138 private final boolean invokeOnRelease; 139 140 // The maximum response time value in milliseconds. 141 private final long maxResponseTimeMillis; 142 143 // The search request that will be used to retrieve the monitor entry. 144 @NotNull private final SearchRequest searchRequest; 145 146 147 148 /** 149 * Creates a new instance of this LDAP connection pool health check with the 150 * provided information. 151 * 152 * @param invokeOnCreate 153 * Indicates whether to test for the existence of the target 154 * entry whenever a new connection is created for use in the 155 * pool. Note that this check will be performed immediately 156 * after the connection has been established and before any 157 * attempt has been made to authenticate that connection. 158 * @param invokeAfterAuthentication 159 * Indicates whether to test for the existence of the target 160 * entry immediately after a connection has been authenticated. 161 * This includes immediately after a newly-created connection 162 * has been authenticated, after a call to the connection pool's 163 * {@code bindAndRevertAuthentication} method, and after a call 164 * to the connection pool's 165 * {@code releaseAndReAuthenticateConnection} method. Note that 166 * even if this is {@code true}, the health check will only be 167 * performed if the provided bind result indicates that the bind 168 * was successful. 169 * @param invokeOnCheckout 170 * Indicates whether to test for the existence of the target 171 * entry immediately before a connection is checked out of the 172 * pool. 173 * @param invokeOnRelease 174 * Indicates whether to test for the existence of the target 175 * entry immediately after a connection has been released back 176 * to the pool. 177 * @param invokeForBackgroundChecks 178 * Indicates whether to test for the existence of the target 179 * entry during periodic background health checks. 180 * @param invokeOnException 181 * Indicates whether to test for the existence of the target 182 * entry if an exception is encountered when using the 183 * connection. 184 * @param maxResponseTimeMillis 185 * The maximum length of time, in milliseconds, to wait for the 186 * monitor entry to be retrieved. If the monitor entry cannot be 187 * retrieved within this length of time, the health check will 188 * fail. If the provided value is less than or equal to zero, 189 * then a default timeout of 5,000 milliseconds (5 seconds) will 190 * be used. 191 */ 192 public LockdownModeLDAPConnectionPoolHealthCheck( 193 final boolean invokeOnCreate, 194 final boolean invokeAfterAuthentication, 195 final boolean invokeOnCheckout, 196 final boolean invokeOnRelease, 197 final boolean invokeForBackgroundChecks, 198 final boolean invokeOnException, 199 final long maxResponseTimeMillis) 200 { 201 this.invokeOnCreate = invokeOnCreate; 202 this.invokeAfterAuthentication = invokeAfterAuthentication; 203 this.invokeOnCheckout = invokeOnCheckout; 204 this.invokeOnRelease = invokeOnRelease; 205 this.invokeForBackgroundChecks = invokeForBackgroundChecks; 206 this.invokeOnException = invokeOnException; 207 208 if (maxResponseTimeMillis > 0L) 209 { 210 this.maxResponseTimeMillis = maxResponseTimeMillis; 211 } 212 else 213 { 214 this.maxResponseTimeMillis = DEFAULT_MAX_RESPONSE_TIME_MILLIS; 215 } 216 217 int timeLimitSeconds = (int) (this.maxResponseTimeMillis / 1_000L); 218 if ((this.maxResponseTimeMillis % 1_000L) != 0L) 219 { 220 timeLimitSeconds++; 221 } 222 223 searchRequest = new SearchRequest(STATUS_HEALTH_SUMMARY_MONITOR_ENTRY_DN, 224 SearchScope.BASE, DereferencePolicy.NEVER, 1, timeLimitSeconds, false, 225 Filter.createANDFilter(), IS_IN_LOCKDOWN_MODE_ATTRIBUTE_NAME); 226 searchRequest.setResponseTimeoutMillis(this.maxResponseTimeMillis); 227 } 228 229 230 231 /** 232 * {@inheritDoc} 233 */ 234 @Override() 235 public void ensureNewConnectionValid(@NotNull final LDAPConnection connection) 236 throws LDAPException 237 { 238 if (invokeOnCreate) 239 { 240 checkForLockdownMode(connection); 241 } 242 } 243 244 245 246 /** 247 * {@inheritDoc} 248 */ 249 @Override() 250 public void ensureConnectionValidAfterAuthentication( 251 @NotNull final LDAPConnection connection, 252 @NotNull final BindResult bindResult) 253 throws LDAPException 254 { 255 if (invokeAfterAuthentication && 256 (bindResult.getResultCode() == ResultCode.SUCCESS)) 257 { 258 checkForLockdownMode(connection); 259 } 260 } 261 262 263 264 /** 265 * {@inheritDoc} 266 */ 267 @Override() 268 public void ensureConnectionValidForCheckout( 269 @NotNull final LDAPConnection connection) 270 throws LDAPException 271 { 272 if (invokeOnCheckout) 273 { 274 checkForLockdownMode(connection); 275 } 276 } 277 278 279 280 /** 281 * {@inheritDoc} 282 */ 283 @Override() 284 public void ensureConnectionValidForRelease( 285 @NotNull final LDAPConnection connection) 286 throws LDAPException 287 { 288 if (invokeOnRelease) 289 { 290 checkForLockdownMode(connection); 291 } 292 } 293 294 295 296 /** 297 * {@inheritDoc} 298 */ 299 @Override() 300 public void ensureConnectionValidForContinuedUse( 301 @NotNull final LDAPConnection connection) 302 throws LDAPException 303 { 304 if (invokeForBackgroundChecks) 305 { 306 checkForLockdownMode(connection); 307 } 308 } 309 310 311 312 /** 313 * {@inheritDoc} 314 */ 315 @Override() 316 public void ensureConnectionValidAfterException( 317 @NotNull final LDAPConnection connection, 318 @NotNull final LDAPException exception) 319 throws LDAPException 320 { 321 if (invokeOnException && 322 (! ResultCode.isConnectionUsable(exception.getResultCode()))) 323 { 324 checkForLockdownMode(connection); 325 } 326 } 327 328 329 330 /** 331 * Indicates whether this health check will check for lockdown mode whenever a 332 * new connection is created. 333 * 334 * @return {@code true} if this health check will check for lockdown mode 335 * whenever a new connection is created, or {@code false} if not. 336 */ 337 public boolean invokeOnCreate() 338 { 339 return invokeOnCreate; 340 } 341 342 343 344 /** 345 * Indicates whether this health check will check for lockdown mode after a 346 * connection has been authenticated, including after authenticating a 347 * newly-created connection, as well as after calls to the connection pool's 348 * {@code bindAndRevertAuthentication} and 349 * {@code releaseAndReAuthenticateConnection} methods. 350 * 351 * @return {@code true} if this health check will check for lockdown mode 352 * whenever a connection has been authenticated, or {@code false} if 353 * not. 354 */ 355 public boolean invokeAfterAuthentication() 356 { 357 return invokeAfterAuthentication; 358 } 359 360 361 362 /** 363 * Indicates whether this health check will check for lockdown mode whenever a 364 * connection is to be checked out for use. 365 * 366 * @return {@code true} if this health check will check for lockdown mode 367 * whenever a connection is to be checked out, or {@code false} if 368 * not. 369 */ 370 public boolean invokeOnCheckout() 371 { 372 return invokeOnCheckout; 373 } 374 375 376 377 /** 378 * Indicates whether this health check will check for lockdown mode whenever a 379 * connection is to be released back to the pool. 380 * 381 * @return {@code true} if this health check will check for lockdown mode 382 * whenever a connection is to be released, or {@code false} if not. 383 */ 384 public boolean invokeOnRelease() 385 { 386 return invokeOnRelease; 387 } 388 389 390 391 /** 392 * Indicates whether this health check will check for lockdown mode during 393 * periodic background health checks. 394 * 395 * @return {@code true} if this health check will check for lockdown mode 396 * during periodic background health checks, or {@code false} if not. 397 */ 398 public boolean invokeForBackgroundChecks() 399 { 400 return invokeForBackgroundChecks; 401 } 402 403 404 405 /** 406 * Indicates whether this health check will check for lockdown mode if an 407 * exception is caught while processing an operation on a connection. 408 * 409 * @return {@code true} if this health check will check for lockdown mode 410 * whenever an exception is caught, or {@code false} if not. 411 */ 412 public boolean invokeOnException() 413 { 414 return invokeOnException; 415 } 416 417 418 419 /** 420 * Retrieves the maximum length of time in milliseconds that this health 421 * check should wait for the target monitor entry to be returned. 422 * 423 * @return The maximum length of time in milliseconds that this health check 424 * should wait for the target monitor entry to be returned. 425 */ 426 public long getMaxResponseTimeMillis() 427 { 428 return maxResponseTimeMillis; 429 } 430 431 432 433 /** 434 * Retrieves the status health summary monitor entry and uses it to determine 435 * whether the server is currently in lockdown mode. If the server is in 436 * lockdown mode, or if a problem occurs while attempting to amek the 437 * determination, then an exception will be thrown. 438 * 439 * @param conn The connection to be checked. 440 * 441 * @throws LDAPException If a problem occurs while trying to retrieve the 442 * target monitor entry, if it cannot be retrieved in 443 * an acceptable length of time, or if the server 444 * reports that it is in lockdown mode. 445 */ 446 private void checkForLockdownMode(@NotNull final LDAPConnection conn) 447 throws LDAPException 448 { 449 final SearchResultEntry monitorEntry; 450 try 451 { 452 monitorEntry = conn.searchForEntry(searchRequest.duplicate()); 453 } 454 catch (final LDAPException e) 455 { 456 Debug.debugException(e); 457 458 final String message = 459 ERR_LOCKDOWN_MODE_HEALTH_CHECK_ERROR_GETTING_MONITOR_ENTRY.get( 460 STATUS_HEALTH_SUMMARY_MONITOR_ENTRY_DN, conn.getHostPort(), 461 StaticUtils.getExceptionMessage(e)); 462 conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, message, 463 e); 464 throw new LDAPException(e.getResultCode(), message, e); 465 } 466 467 468 if (monitorEntry == null) 469 { 470 final String message = 471 ERR_LOCKDOWN_MODE_HEALTH_CHECK_NO_MONITOR_ENTRY.get( 472 STATUS_HEALTH_SUMMARY_MONITOR_ENTRY_DN, conn.getHostPort()); 473 conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, message, 474 null); 475 throw new LDAPException(ResultCode.NO_RESULTS_RETURNED, message); 476 } 477 478 479 final Boolean isInLockdownMode = monitorEntry.getAttributeValueAsBoolean( 480 IS_IN_LOCKDOWN_MODE_ATTRIBUTE_NAME); 481 if (isInLockdownMode == null) 482 { 483 final String message = 484 ERR_LOCKDOWN_MODE_HEALTH_CHECK_NO_MONITOR_ATTR.get( 485 STATUS_HEALTH_SUMMARY_MONITOR_ENTRY_DN, conn.getHostPort(), 486 IS_IN_LOCKDOWN_MODE_ATTRIBUTE_NAME); 487 conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, message, 488 null); 489 throw new LDAPException(ResultCode.NO_SUCH_ATTRIBUTE, message); 490 } 491 else if (Boolean.TRUE.equals(isInLockdownMode)) 492 { 493 final String message = 494 ERR_LOCKDOWN_MODE_HEALTH_CHECK_IS_IN_LOCKDOWN_MODE.get( 495 conn.getHostPort()); 496 conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, message, 497 null); 498 throw new LDAPException(ResultCode.UNAVAILABLE, message); 499 } 500 } 501 502 503 504 /** 505 * {@inheritDoc} 506 */ 507 @Override() 508 public void toString(@NotNull final StringBuilder buffer) 509 { 510 buffer.append("LockdownMOdeLDAPConnectionPoolHealthCheck(invokeOnCreate="); 511 buffer.append(invokeOnCreate); 512 buffer.append(", invokeAfterAuthentication="); 513 buffer.append(invokeAfterAuthentication); 514 buffer.append(", invokeOnCheckout="); 515 buffer.append(invokeOnCheckout); 516 buffer.append(", invokeOnRelease="); 517 buffer.append(invokeOnRelease); 518 buffer.append(", invokeForBackgroundChecks="); 519 buffer.append(invokeForBackgroundChecks); 520 buffer.append(", invokeOnException="); 521 buffer.append(invokeOnException); 522 buffer.append(", maxResponseTimeMillis="); 523 buffer.append(maxResponseTimeMillis); 524 buffer.append(')'); 525 } 526}