/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * docs/licenses/cddl.txt * or http://www.opensource.org/licenses/cddl1.php. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * docs/licenses/cddl.txt. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Portions Copyright 2010-2023 Ping Identity Corporation */ package com.unboundid.directory.sdk.examples.groovy; import java.util.List; import com.unboundid.directory.sdk.proxy.config.LDAPHealthCheckConfig; import com.unboundid.directory.sdk.proxy.scripting.ScriptedLDAPHealthCheck; import com.unboundid.directory.sdk.proxy.types.BackendServer; import com.unboundid.directory.sdk.proxy.types.CompletedProxyOperationContext; import com.unboundid.directory.sdk.proxy.types.HealthCheckResult; import com.unboundid.directory.sdk.proxy.types.HealthCheckState; import com.unboundid.directory.sdk.proxy.types.ProxyServerContext; import com.unboundid.ldap.sdk.Filter; import com.unboundid.ldap.sdk.LDAPConnection; import com.unboundid.ldap.sdk.LDAPException; import com.unboundid.ldap.sdk.LDAPSearchException; import com.unboundid.ldap.sdk.ResultCode; import com.unboundid.ldap.sdk.SearchRequest; import com.unboundid.ldap.sdk.SearchResult; import com.unboundid.ldap.sdk.SearchScope; import com.unboundid.util.StaticUtils; import com.unboundid.util.args.ArgumentException; import com.unboundid.util.args.ArgumentParser; import com.unboundid.util.args.DNArgument; import com.unboundid.util.args.IntegerArgument; /** * This class provides a simple example of an LDAP health check which simply * attempts to retrieve a specified entry from the backend server. The length * of time required to retrieve the entry will be used to help determine the * health check score. It has the following configuration arguments: * <UL> * <LI>entry-dn -- The DN of the entry to retrieve.</LI> * <LI>max-available-response-time -- The maximum search duration to consider * a server available. Any duration longer than this will cause the * server to be considered either degraded or unavailable.</LI> * <LI>max-degraded-response-time -- The maximum search duration to consider a * server degraded. Any duration longer than this will cause the server * to be considered unavailable.</LI> * </UL> */ public final class ExampleScriptedLDAPHealthCheck extends ScriptedLDAPHealthCheck { /** * The name of the argument that will be used to specify the DN of the entry * to retrieve. */ private static final String ARG_NAME_ENTRY_DN = "entry-dn"; /** * The name of the argument that will be used to specify the maximum available * response time. */ private static final String ARG_NAME_MAX_AVAILABLE_DURATION = "max-available-response-time-millis"; /** * The name of the argument that will be used to specify the maximum degraded * response time. */ private static final String ARG_NAME_MAX_DEGRADED_DURATION = "max-degraded-response-time-millis"; // The maximum available duration. private volatile long maxAvailableDuration; // The maximum degraded duration. private volatile long maxDegradedDuration; // The server context for the server in which this extension is running. private ProxyServerContext serverContext; // The DN of the entry to retrieve. private volatile String entryDN; /** * Creates a new instance of this LDAP health check. All LDAP health check * implementations must include a default constructor, but any initialization * should generally be done in the {@code initializeLDAPHealthCheck} method. */ public ExampleScriptedLDAPHealthCheck() { // No implementation required. } /** * Updates the provided argument parser to define any configuration arguments * which may be used by this LDAP health check. The argument parser may also * be updated to define relationships between arguments (e.g., to specify * required, exclusive, or dependent argument sets). * * @param parser The argument parser to be updated with the configuration * arguments which may be used by this LDAP health check. * * @throws ArgumentException If a problem is encountered while updating the * provided argument parser. */ @Override() public void defineConfigArguments(final ArgumentParser parser) throws ArgumentException { // Add an argument that allows you to specify the target entry. Character shortIdentifier = null; String longIdentifier = ARG_NAME_ENTRY_DN; boolean required = true; int maxOccurrences = 1; String placeholder = "{dn}"; String description = "The DN of the entry to retrieve during the " + "course of health check processing."; parser.addArgument(new DNArgument(shortIdentifier, longIdentifier, required, maxOccurrences, placeholder, description)); // Add an argument that allows you to specify the maximum available // duration. shortIdentifier = null; longIdentifier = ARG_NAME_MAX_AVAILABLE_DURATION; required = true; maxOccurrences = 1; placeholder = "{duration}"; description = "The maximum length of time that a health check search " + "may take for a server to be considered available. The value " + "should consist of an integer followed by a time unit (e.g., " + "'10 ms')."; int lowerBound = 1; int upperBound = Integer.MAX_VALUE; Integer defaultValue = null; parser.addArgument(new IntegerArgument(shortIdentifier, longIdentifier, required, maxOccurrences, placeholder, description, lowerBound, upperBound, defaultValue)); // Add an argument that allows you to specify the maximum degraded // duration. shortIdentifier = null; longIdentifier = ARG_NAME_MAX_DEGRADED_DURATION; required = true; placeholder = "{duration}"; description = "The maximum length of time that a health check search " + "may take for a server to be considered degraded. The value " + "should consist of an integer followed by a time unit (e.g., " + "'10 ms')."; lowerBound = 1; upperBound = Integer.MAX_VALUE; defaultValue = null; parser.addArgument(new IntegerArgument(shortIdentifier, longIdentifier, required, maxOccurrences, placeholder, description, lowerBound, upperBound, defaultValue)); } /** * Initializes this LDAP health check. * * @param serverContext A handle to the server context for the server in * which this extension is running. * @param config The general configuration for this LDAP health * check. * @param parser The argument parser which has been initialized from * the configuration for this LDAP health check. * * @throws LDAPException If a problem occurs while initializing this LDAP * health check. */ @Override() public void initializeLDAPHealthCheck(final ProxyServerContext serverContext, final LDAPHealthCheckConfig config, final ArgumentParser parser) throws LDAPException { serverContext.debugInfo("Beginning LDAP health check initialization"); this.serverContext = serverContext; // Get the target entry DN. entryDN = ((DNArgument) parser.getNamedArgument(ARG_NAME_ENTRY_DN)).getValue().toString(); // Get the maximum available response time. maxAvailableDuration = ((IntegerArgument) parser.getNamedArgument(ARG_NAME_MAX_AVAILABLE_DURATION)).getValue(); // Get the maximum degraded response time. maxDegradedDuration = ((IntegerArgument) parser.getNamedArgument(ARG_NAME_MAX_DEGRADED_DURATION)).getValue(); // The maximum available response time must be less than or equal to the // maximum degraded response time. if (maxAvailableDuration > maxDegradedDuration) { throw new LDAPException(ResultCode.PARAM_ERROR, "The maximum available duration must be less than or equal to the " + "maximum degraded duration."); } } /** * Indicates whether the configuration contained in the provided argument * parser represents a valid configuration for this extension. * * @param config The general configuration for this LDAP health * check. * @param parser The argument parser which has been initialized * with the proposed configuration. * @param unacceptableReasons A list that can be updated with reasons that * the proposed configuration is not acceptable. * * @return {@code true} if the proposed configuration is acceptable, or * {@code false} if not. */ @Override() public boolean isConfigurationAcceptable(final LDAPHealthCheckConfig config, final ArgumentParser parser, final List<String> unacceptableReasons) { boolean acceptable = true; // The maximum available response time must be less than or equal to the // maximum degraded response time. // Get the target entry DN. final int maxAvailable = ((IntegerArgument) parser.getNamedArgument(ARG_NAME_MAX_AVAILABLE_DURATION)).getValue(); final int maxDegraded = ((IntegerArgument) parser.getNamedArgument(ARG_NAME_MAX_DEGRADED_DURATION)).getValue(); if (maxAvailable > maxDegraded) { unacceptableReasons.add("The maximum available duration must be less " + "than or equal to the maximum degraded duration."); acceptable = false; } return acceptable; } /** * Attempts to apply the configuration contained in the provided argument * parser. * * @param config The general configuration for this LDAP * health check. * @param parser The argument parser which has been * initialized with the new configuration. * @param adminActionsRequired A list that can be updated with information * about any administrative actions that may be * required before one or more of the * configuration changes will be applied. * @param messages A list that can be updated with information * about the result of applying the new * configuration. * * @return A result code that provides information about the result of * attempting to apply the configuration change. */ @Override() public ResultCode applyConfiguration(final LDAPHealthCheckConfig config, final ArgumentParser parser, final List<String> adminActionsRequired, final List<String> messages) { // Get the target entry DN. final String newDN = ((DNArgument) parser.getNamedArgument(ARG_NAME_ENTRY_DN)).getValue().toString(); // Get the maximum available response time. final int newAvailable = ((IntegerArgument) parser.getNamedArgument(ARG_NAME_MAX_AVAILABLE_DURATION)).getValue(); // Get the maximum degraded response time. final int newDegraded = ((IntegerArgument) parser.getNamedArgument(ARG_NAME_MAX_DEGRADED_DURATION)).getValue(); entryDN = newDN; maxAvailableDuration = newAvailable; maxDegradedDuration = newDegraded; return ResultCode.SUCCESS; } /** * Performs any cleanup which may be necessary when this LDAP health check is * to be taken out of service. */ @Override() public void finalizeLDAPHealthCheck() { // No finalization is required. } /** * Attempts to determine the health of the provided LDAP external server whose * last health check result indicated that the server had a state of * AVAILABLE. This method may be periodically invoked for AVAILABLE servers * to determine whether their state has changed. * * @param backendServer A handle to the LDAP external server whose health is * to be assessed. * @param connection A connection that may be used to communicate with * the server in the course of performing the * evaluation. The health check should not do anything * which may alter the state of this connection. * * @return Information about the result of the health check. */ @Override() public HealthCheckResult checkAvailableServer( final BackendServer backendServer, final LDAPConnection connection) { return checkServer(connection); } /** * Attempts to determine the health of the provided LDAP external server whose * last health check result indicated that the server had a state of DEGRADED. * This method may be periodically invoked for DEGRADED servers to determine * whether their state has changed. * * @param backendServer A handle to the LDAP external server whose health is * to be assessed. * @param connection A connection that may be used to communicate with * the server in the course of performing the * evaluation. The health check should not do anything * which may alter the state of this connection. * * @return Information about the result of the health check. */ @Override() public HealthCheckResult checkDegradedServer( final BackendServer backendServer, final LDAPConnection connection) { return checkServer(connection); } /** * Attempts to determine the health of the provided LDAP external server whose * last health check result indicated that the server had a state of * UNAVAILABLE. This method may be periodically invoked for UNAVAILABLE * servers to determine whether their state has changed. * * @param backendServer A handle to the LDAP external server whose health is * to be assessed. * @param connection A connection that may be used to communicate with * the server in the course of performing the * evaluation. The health check should not do anything * which may alter the state of this connection. * * @return Information about the result of the health check. */ @Override() public HealthCheckResult checkUnavailableServer( final BackendServer backendServer, final LDAPConnection connection) { return checkServer(connection); } /** * Attempts to determine the health of the provided LDAP external server in * which an attempted operation did not complete successfully. * * @param operationContext A handle to the operation context for the * operation that failed. * @param exception The exception caught when attempting to process * the associated operation in the backend server. * @param backendServer A handle to the backend server in which the * operation was processed. * * @return Information about the result of the health check. */ @Override() public HealthCheckResult checkFailedOperation( final CompletedProxyOperationContext operationContext, final LDAPException exception, final BackendServer backendServer) { // Look at the result code to see if it indicates that the server might not // be available. if (exception.getResultCode().isConnectionUsable()) { // The result code indicates that the connection is probably usable, so // we'll just return whatever the last known result was. return backendServer.getHealthCheckResult(); } // The server might not be usable. See if we can establish a connection to // it. final LDAPConnection connection; try { connection = backendServer.createNewConnection(null, "Example Health Check for failed operation " + operationContext.toString()); } catch (final Exception e) { // We can't establish a connection, so we have to consider the server // unavailable. serverContext.debugCaught(e); return serverContext.createHealthCheckResult( HealthCheckState.UNAVAILABLE, 0, "Unable to establish a connection to the server: " + StaticUtils.getExceptionMessage(e)); } // Use the connection to perform the health check. try { return checkServer(connection); } finally { connection.close(); } } /** * Performs a search to assess the health of the backend server using the * given connection. * * @param connection The connection to use to communicate with the server. * * @return The health check result representing the assessed health of the * server. */ private HealthCheckResult checkServer(final LDAPConnection connection) { // Create local copies of the config variables. final String dn = entryDN; final long maxA = maxAvailableDuration; final long maxD = maxDegradedDuration; // Construct a search request to use for the health check. final SearchRequest searchRequest = new SearchRequest(dn, SearchScope.BASE, Filter.createPresenceFilter("objectClass"), SearchRequest.NO_ATTRIBUTES); searchRequest.setResponseTimeoutMillis(maxDegradedDuration); // Get the start time. final long startTime = System.currentTimeMillis(); // Perform a search to retrieve the target entry SearchResult searchResult; try { searchResult = connection.search(searchRequest); } catch (final LDAPSearchException lse) { serverContext.debugCaught(lse); searchResult = lse.getSearchResult(); } // Get the stop time. final long stopTime = System.currentTimeMillis(); // If the result code is anything other than success, then we'll consider // the server unavailable. if (searchResult.getResultCode() != ResultCode.SUCCESS) { return serverContext.createHealthCheckResult( HealthCheckState.UNAVAILABLE, 0, "Example health check search returned a non-success result of " + searchResult.toString()); } // Figure out how long the search took and use that to determine the state // and score to use for the server. final long elapsedTime = stopTime - startTime; if (elapsedTime <= maxA) { final int score = (int) Math.round(10.0d - (10.0d * elapsedTime / maxA)); return serverContext.createHealthCheckResult( HealthCheckState.AVAILABLE, score); } else if (elapsedTime <= maxD) { final int score = (int) Math.round(10.0d - (10.0d * elapsedTime / maxD)); return serverContext.createHealthCheckResult( HealthCheckState.DEGRADED, score, "Example health check duration exceeded the maximum available " + "response time of " + maxA + "ms"); } else { return serverContext.createHealthCheckResult( HealthCheckState.UNAVAILABLE, 0, "Example health check duration exceeded the maximum degraded " + "response time of " + maxD + "ms"); } } }