001    /*
002     * CDDL HEADER START
003     *
004     * The contents of this file are subject to the terms of the
005     * Common Development and Distribution License, Version 1.0 only
006     * (the "License").  You may not use this file except in compliance
007     * with the License.
008     *
009     * You can obtain a copy of the license at
010     * docs/licenses/cddl.txt
011     * or http://www.opensource.org/licenses/cddl1.php.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * docs/licenses/cddl.txt.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2010-2013 UnboundID Corp.
026     */
027    package com.unboundid.directory.sdk.proxy.api;
028    
029    
030    
031    import java.util.Collections;
032    import java.util.List;
033    import java.util.Map;
034    
035    import com.unboundid.directory.sdk.common.internal.ExampleUsageProvider;
036    import com.unboundid.directory.sdk.common.internal.Reconfigurable;
037    import com.unboundid.directory.sdk.common.internal.UnboundIDExtension;
038    import com.unboundid.directory.sdk.proxy.config.LDAPHealthCheckConfig;
039    import com.unboundid.directory.sdk.proxy.internal.DirectoryProxyServerExtension;
040    import com.unboundid.directory.sdk.proxy.types.BackendServer;
041    import com.unboundid.directory.sdk.proxy.types.CompletedProxyOperationContext;
042    import com.unboundid.directory.sdk.proxy.types.HealthCheckResult;
043    import com.unboundid.directory.sdk.proxy.types.ProxyServerContext;
044    import com.unboundid.ldap.sdk.LDAPConnection;
045    import com.unboundid.ldap.sdk.LDAPException;
046    import com.unboundid.ldap.sdk.ResultCode;
047    import com.unboundid.util.Extensible;
048    import com.unboundid.util.ThreadSafety;
049    import com.unboundid.util.ThreadSafetyLevel;
050    import com.unboundid.util.args.ArgumentException;
051    import com.unboundid.util.args.ArgumentParser;
052    
053    
054    
055    /**
056     * This class defines an API that must be implemented by extensions which are
057     * used to assess the health of backend servers accessed through the Directory
058     * Proxy Server.  Each health check invocation generates a health check result,
059     * which contains the following elements:
060     * <UL>
061     *   <LI>Health Check State -- This indicates the general health state for the
062     *       backend server.  The state may be AVAILABLE (the server is completely
063     *       suitable for use), DEGRADED (the server should be avoided if there are
064     *       better servers but may be used if necessary), or UNAVAILABLE (the
065     *       server not not be used at all).</LI>
066     *   <LI>Health Check Score -- This provides an integer value between 10 (the
067     *       best score) and 1 (the worst score) that may help rank servers with the
068     *       same state.  Some load-balancing algorithms (e.g., the health-weighted
069     *       algorithm) may use this to prefer servers with a higher score over
070     *       those with a lower score.  Servers with a state of UNAVAILABLE will
071     *       always have a score of zero.</LI>
072     *   <LI>Messages -- This may optionally provide information about the reason
073     *       for the state and/or score.  This is primarily useful for results
074     *       indicating that the server is DEGRADED or UNAVAILABLE to provide
075     *       information about the problem(s) preventing it from being considered
076     *       AVAILABLE.</LI>
077     * </UL>
078     * <BR><BR>
079     * LDAP health checks may be invoked in two ways.  They will be invoked on a
080     * regular basis to periodically re-assess the health of each backend server,
081     * but they may also be invoked after a failed operation in order to more
082     * quickly detect a problem that should cause the server to be transitioned to a
083     * less-available state.  The server will ensure that no more than one health
084     * check is in progress at a time for a given server in order to avoid
085     * overwhelming it with health checking, but it is still recommended that
086     * health checks triggered as a result of a failed operation attempt to use the
087     * operation result code to decide whether it may be necessary to actually
088     * attempt to communicate with the server.
089     * <BR><BR>
090     * Further, it may also be useful to have more stringent criteria for promoting
091     * the health of a server than for demoting it in order to avoid a ping-pong
092     * effect that may occur if a server is hovering near the border between
093     * AVAILABLE and DEGRADED or between DEGRADED and UNAVAILABLE.  For example, if
094     * a health check attempts to perform an operation in the backend server and the
095     * response time for that operation is taken into account when determining the
096     * server health, it might be better to require a lower response time in order
097     * to transition from DEGRADED to AVAILABLE than was originally required to
098     * transition from AVAILABLE to DEGRADED.
099     * <BR>
100     * <H2>Configuring LDAP Health Checks</H2>
101     * In order to configure an LDAP health check created using this API, use a
102     * command like:
103     * <PRE>
104     *      dsconfig create-ldap-health-check \
105     *           --check-name "<I>{check-name}</I>" \
106     *           --type third-party \
107     *           --set enabled:true \
108     *           --set "extension-class:<I>{class-name}</I>" \
109     *           --set "extension-argument:<I>{name=value}</I>"
110     * </PRE>
111     * where "<I>{check-name}</I>" is the name to use for the LDAP health check
112     * instance, "<I>{class-name}</I>" is the fully-qualified name of the Java class
113     * that extends {@code com.unboundid.directory.sdk.proxy.api.LDAPHealthCheck},
114     * and "<I>{name=value}</I>" represents name-value pairs for any arguments to
115     * provide to the LDAP health check.  If multiple arguments should be provided
116     * to the LDAP health check, then the
117     * "<CODE>--set extension-argument:<I>{name=value}</I></CODE>" option should be
118     * provided multiple times.
119     *
120     * @see  com.unboundid.directory.sdk.proxy.scripting.ScriptedLDAPHealthCheck
121     */
122    @Extensible()
123    @DirectoryProxyServerExtension(appliesToLocalContent=false,
124         appliesToRemoteContent=true)
125    @ThreadSafety(level=ThreadSafetyLevel.INTERFACE_THREADSAFE)
126    public abstract class LDAPHealthCheck
127           implements UnboundIDExtension, Reconfigurable<LDAPHealthCheckConfig>,
128                      ExampleUsageProvider
129    {
130      /**
131       * Creates a new instance of this LDAP health check.  All LDAP health check
132       * implementations must include a default constructor, but any initialization
133       * should generally be done in the {@code initializeLDAPHealthCheck} method.
134       */
135      public LDAPHealthCheck()
136      {
137        // No implementation is required.
138      }
139    
140    
141    
142      /**
143       * {@inheritDoc}
144       */
145      public abstract String getExtensionName();
146    
147    
148    
149      /**
150       * {@inheritDoc}
151       */
152      public abstract String[] getExtensionDescription();
153    
154    
155    
156      /**
157       * {@inheritDoc}
158       */
159      public void defineConfigArguments(final ArgumentParser parser)
160             throws ArgumentException
161      {
162        // No arguments will be allowed by default.
163      }
164    
165    
166    
167      /**
168       * Initializes this LDAP health check.
169       *
170       * @param  serverContext  A handle to the server context for the Directory
171       *                        Proxy server in which this extension is running.
172       * @param  config         The general configuration for this LDAP health
173       *                        check.
174       * @param  parser         The argument parser which has been initialized from
175       *                        the configuration for this LDAP health check.
176       *
177       * @throws  LDAPException  If a problem occurs while initializing this LDAP
178       *                         health check.
179       */
180      public void initializeLDAPHealthCheck(final ProxyServerContext serverContext,
181                                            final LDAPHealthCheckConfig config,
182                                            final ArgumentParser parser)
183             throws LDAPException
184      {
185        // No initialization will be performed by default.
186      }
187    
188    
189    
190      /**
191       * {@inheritDoc}
192       */
193      public boolean isConfigurationAcceptable(final LDAPHealthCheckConfig config,
194                          final ArgumentParser parser,
195                          final List<String> unacceptableReasons)
196      {
197        // No extended validation will be performed by default.
198        return true;
199      }
200    
201    
202    
203      /**
204       * {@inheritDoc}
205       */
206      public ResultCode applyConfiguration(final LDAPHealthCheckConfig config,
207                                           final ArgumentParser parser,
208                                           final List<String> adminActionsRequired,
209                                           final List<String> messages)
210      {
211        // By default, no configuration changes will be applied.  If there are any
212        // arguments, then add an admin action message indicating that the extension
213        // needs to be restarted for any changes to take effect.
214        if (! parser.getNamedArguments().isEmpty())
215        {
216          adminActionsRequired.add(
217               "No configuration change has actually been applied.  The new " +
218                    "configuration will not take effect until this LDAP health " +
219                    "check is disabled and re-enabled or until the server is " +
220                    "restarted.");
221        }
222    
223        return ResultCode.SUCCESS;
224      }
225    
226    
227    
228      /**
229       * Performs any cleanup which may be necessary when this LDAP health check is
230       * to be taken out of service.
231       */
232      public void finalizeLDAPHealthCheck()
233      {
234        // No implementation is required.
235      }
236    
237    
238    
239      /**
240       * Attempts to determine the health of the provided LDAP external server whose
241       * last health check result indicated that the server had a state of
242       * AVAILABLE.  This method may be periodically invoked for AVAILABLE servers
243       * to determine whether their state has changed.
244       *
245       * @param  backendServer  A handle to the LDAP external server whose health is
246       *                        to be assessed.
247       * @param  connection     A connection that may be used to communicate with
248       *                        the server in the course of performing the
249       *                        evaluation.  The health check should not do anything
250       *                        which may alter the state of this connection.
251       *
252       * @return  Information about the result of the health check.
253       */
254      public abstract HealthCheckResult checkAvailableServer(
255                                             final BackendServer backendServer,
256                                             final LDAPConnection connection);
257    
258    
259    
260      /**
261       * Attempts to determine the health of the provided LDAP external server whose
262       * last health check result indicated that the server had a state of DEGRADED.
263       * This method may be periodically invoked for DEGRADED servers to determine
264       * whether their state has changed.
265       *
266       * @param  backendServer  A handle to the LDAP external server whose health is
267       *                        to be assessed.
268       * @param  connection     A connection that may be used to communicate with
269       *                        the server in the course of performing the
270       *                        evaluation.  The health check should not do anything
271       *                        which may alter the state of this connection.
272       *
273       * @return  Information about the result of the health check.
274       */
275      public abstract HealthCheckResult checkDegradedServer(
276                                             final BackendServer backendServer,
277                                             final LDAPConnection connection);
278    
279    
280    
281      /**
282       * Attempts to determine the health of the provided LDAP external server whose
283       * last health check result indicated that the server had a state of
284       * UNAVAILABLE.  This method may be periodically invoked for UNAVAILABLE
285       * servers to determine whether their state has changed.
286       *
287       * @param  backendServer  A handle to the LDAP external server whose health is
288       *                        to be assessed.
289       * @param  connection     A connection that may be used to communicate with
290       *                        the server in the course of performing the
291       *                        evaluation.  The health check should not do anything
292       *                        which may alter the state of this connection.
293       *
294       * @return  Information about the result of the health check.
295       */
296      public abstract HealthCheckResult checkUnavailableServer(
297                                             final BackendServer backendServer,
298                                             final LDAPConnection connection);
299    
300    
301    
302      /**
303       * Attempts to determine the health of the provided LDAP external server in
304       * which an attempted operation did not complete successfully.
305       *
306       * @param  operationContext  A handle to the operation context for the
307       *                           operation that failed.
308       * @param  exception         The exception caught when attempting to process
309       *                           the associated operation in the backend server.
310       * @param  backendServer     A handle to the backend server in which the
311       *                           operation was processed.
312       *
313       * @return  Information about the result of the health check.
314       */
315      public abstract HealthCheckResult checkFailedOperation(
316                           final CompletedProxyOperationContext operationContext,
317                           final LDAPException exception,
318                           final BackendServer backendServer);
319    
320    
321    
322      /**
323       * Retrieves a string representation of this LDAP health check.
324       *
325       * @return  A string representation of this LDAP health check.
326       */
327      @Override()
328      public final String toString()
329      {
330        final StringBuilder buffer = new StringBuilder();
331        toString(buffer);
332        return buffer.toString();
333      }
334    
335    
336    
337      /**
338       * Appends a string representation of this LDAP health check to the provided
339       * buffer.
340       *
341       * @param  buffer  The buffer to which the string representation should be
342       *                 appended.
343       */
344      public abstract void toString(final StringBuilder buffer);
345    
346    
347    
348      /**
349       * {@inheritDoc}
350       */
351      public Map<List<String>,String> getExamplesArgumentSets()
352      {
353        return Collections.emptyMap();
354      }
355    }