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 *      Portions Copyright 2014-2024 Ping Identity Corporation
026 */
027package com.unboundid.directory.sdk.proxy.api;
028
029
030
031import java.util.Collections;
032import java.util.List;
033import java.util.Map;
034
035import com.unboundid.directory.sdk.common.internal.ExampleUsageProvider;
036import com.unboundid.directory.sdk.common.internal.Reconfigurable;
037import com.unboundid.directory.sdk.common.internal.UnboundIDExtension;
038import com.unboundid.directory.sdk.proxy.config.ServerAffinityProviderConfig;
039import com.unboundid.directory.sdk.proxy.internal.DirectoryProxyServerExtension;
040import com.unboundid.directory.sdk.proxy.types.BackendServer;
041import com.unboundid.directory.sdk.proxy.types.ProxyOperationContext;
042import com.unboundid.directory.sdk.proxy.types.ProxyServerContext;
043import com.unboundid.directory.sdk.proxy.types.ServerAffinity;
044import com.unboundid.ldap.sdk.DN;
045import com.unboundid.ldap.sdk.LDAPException;
046import com.unboundid.ldap.sdk.ResultCode;
047import com.unboundid.util.Extensible;
048import com.unboundid.util.ThreadSafety;
049import com.unboundid.util.ThreadSafetyLevel;
050import com.unboundid.util.args.ArgumentException;
051import 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 influence which backend server a client should use in a load-balanced
058 * set.  The primary purpose is to attempt to ensure that related requests are
059 * sent to the same backend server in an attempt to eliminate problems that may
060 * result from replication propagation delay (e.g., because of a read
061 * immediately after a write), to ensure that repeated accesses to the same data
062 * benefit from having the data in-cache, etc.  A server affinity provider is
063 * called at two points in operation processing:
064 * <UL>
065 *   <LI>Before using the load-balancing algorithm to select a backend server.
066 *       If the server affinity provider indicates that there is already an
067 *       affinity defined for the operation, and the affinity is for a server
068 *       that has a health check state of AVAILABLE, then the server selected by
069 *       affinity will be used instead selecting a server with the
070 *       load-balancing algorithm.  If the server affinity provider does not
071 *       specify which server to use, or if the server selected by the affinity
072 *       provider does not have a health check state of AVAILABLE, then the
073 *       load-balancing algorithm will be used to select the server.</LI>
074 *   <LI>After an operation has been processed.  This may be used to set or
075 *       update state information that may be used by the next request in a
076 *       "related" set (where "related" is defined according to whatever logic
077 *       the affinity provider provides).</LI>
078 * </UL>
079 * <BR><BR>
080 * <H2>Configuring Server Affinity Providers</H2>
081 * In order to configure a server affinity provider created using this API, use
082 * a command like:
083 * <PRE>
084 *      dsconfig create-server-affinity-provider \
085 *           --provider-name "<I>{provider-name}</I>" \
086 *           --type third-party \
087 *           --set enabled:true \
088 *           --set "extension-class:<I>{class-name}</I>" \
089 *           --set "extension-argument:<I>{name=value}</I>"
090 * </PRE>
091 * where "<I>{provider-name}</I>" is the name to use for the server affinity
092 * provider instance, "<I>{class-name}</I>" is the fully-qualified name of the
093 * Java class that extends
094 * {@code com.unboundid.directory.sdk.proxy.api.ServerAffinityProvider}, and
095 * "<I>{name=value}</I>" represents name-value pairs for any arguments to
096 * provide to the server affinity provider.  If multiple arguments should be
097 * provided to the server affinity provider, then the
098 * "<CODE>--set extension-argument:<I>{name=value}</I></CODE>" option should be
099 * provided multiple times.
100 */
101@Extensible()
102@DirectoryProxyServerExtension(appliesToLocalContent=false,
103     appliesToRemoteContent=true)
104@ThreadSafety(level=ThreadSafetyLevel.INTERFACE_THREADSAFE)
105public abstract class ServerAffinityProvider
106       implements UnboundIDExtension,
107                  Reconfigurable<ServerAffinityProviderConfig>,
108                  ExampleUsageProvider
109{
110  /**
111   * Creates a new instance of this server affinity provider.  All server
112   * affinity provider implementations must include a default constructor, but
113   * any initialization should generally be done in the
114   * {@code initializeServerAffinityProvider} method.
115   */
116  public ServerAffinityProvider()
117  {
118    // No implementation is required.
119  }
120
121
122
123  /**
124   * {@inheritDoc}
125   */
126  @Override()
127  public abstract String getExtensionName();
128
129
130
131  /**
132   * {@inheritDoc}
133   */
134  @Override()
135  public abstract String[] getExtensionDescription();
136
137
138
139  /**
140   * {@inheritDoc}
141   */
142  @Override()
143  public void defineConfigArguments(final ArgumentParser parser)
144         throws ArgumentException
145  {
146    // No arguments will be allowed by default.
147  }
148
149
150
151  /**
152   * Initializes this server affinity provider.
153   *
154   * @param  serverContext  A handle to the server context for the Directory
155   *                        Proxy server in which this extension is running.
156   * @param  config         The general configuration for this server affinity
157   *                        provider.
158   * @param  parser         The argument parser which has been initialized from
159   *                        the configuration for this server affinity provider.
160   *
161   * @throws  LDAPException  If a problem occurs while initializing this server
162   *                         affinity provider.
163   */
164  public void initializeServerAffinityProvider(
165                   final ProxyServerContext serverContext,
166                   final ServerAffinityProviderConfig config,
167                   final ArgumentParser parser)
168         throws LDAPException
169  {
170    // No initialization will be performed by default.
171  }
172
173
174
175  /**
176   * {@inheritDoc}
177   */
178  @Override()
179  public boolean isConfigurationAcceptable(
180                      final ServerAffinityProviderConfig config,
181                      final ArgumentParser parser,
182                      final List<String> unacceptableReasons)
183  {
184    // No extended validation will be performed by default.
185    return true;
186  }
187
188
189
190  /**
191   * {@inheritDoc}
192   */
193  @Override()
194  public ResultCode applyConfiguration(
195                         final ServerAffinityProviderConfig config,
196                         final ArgumentParser parser,
197                         final List<String> adminActionsRequired,
198                         final List<String> messages)
199  {
200    // By default, no configuration changes will be applied.  If there are any
201    // arguments, then add an admin action message indicating that the extension
202    // needs to be restarted for any changes to take effect.
203    if (! parser.getNamedArguments().isEmpty())
204    {
205      adminActionsRequired.add(
206           "No configuration change has actually been applied.  The new " +
207                "configuration will not take effect until this server " +
208                "affinity provider is disabled and re-enabled or until the " +
209                "server is restarted.");
210    }
211
212    return ResultCode.SUCCESS;
213  }
214
215
216
217  /**
218   * Performs any cleanup which may be necessary when this server affinity
219   * provider is to be taken out of service.
220   */
221  public void finalizeServerAffinityProvider()
222  {
223    // No implementation is required.
224  }
225
226
227
228  /**
229   * Clears all affinity data associated with the provided list of
230   * load-balancing algorithms.
231   *
232   * @param  lbaDN           The config entry DN of the load-balancing algorithm
233   *                         for which to clear the affinity data.  If this is
234   *                         {@code null}, then all affinity data for all
235   *                         load-balancing algorithms should be cleared.
236   * @param  backendServers  The set of backend servers that are associated with
237   *                         the specified load-balancing algorithm.
238   */
239  public abstract void clearAffinityData(final DN lbaDN,
240                            final Map<DN,BackendServer> backendServers);
241
242
243
244  /**
245   * Indicates which backend server should be used for the provided operation.
246   * It is generally recommended that this method only return a server if the
247   * operation matches an affinity that has already been established (via a
248   * previous call to the {@code updateAffinity} method).  If no affinity has
249   * been set, then it is recommended that this method return {@code null} to
250   * allow the load-balancing algorithm to select an appropriate server instead.
251   *
252   * @param  lbaDN           The config entry DN of the load-balancing algorithm
253   *                         for which to make the determination.
254   * @param  backendServers  The set of backend servers from which the selection
255   *                         may be made, indexed by the DN of the configuration
256   *                         entry for each server.  It will not be
257   *                         {@code null}.
258   * @param  operation       The operation to be processed.  It will not be
259   *                         {@code null}.
260   *
261   * @return  The backend server for which an affinity is already established,
262   *          or {@code null} if the operation does not match any affinity that
263   *          has already been established and the appropriate backend server
264   *          should be selected by the load-balancing algorithm.
265   */
266  public abstract ServerAffinity selectServer(final DN lbaDN,
267                       final Map<DN,BackendServer> backendServers,
268                       final ProxyOperationContext operation);
269
270
271
272  /**
273   * Specifies the backend server that was used to process the provided
274   * operation, which allows this affinity provider to establish or update any
275   * necessary state information that could be used to select the same server
276   * for "related" operations that may be processed in the future.
277   *
278   * @param  operation      The operation that was processed.
279   * @param  lbaDN          The config entry DN of the load-balancing algorithm
280   *                        with which the backend server is associated.
281   * @param  backendServer  The backend server that was used to process the
282   *                        operation.
283   */
284  public abstract void updateAffinity(
285                            final ProxyOperationContext operation,
286                            final DN lbaDN, final BackendServer backendServer);
287
288
289
290  /**
291   * Retrieves a string representation of this server affinity provider.
292   *
293   * @return  A string representation of this server affinity provider.
294   */
295  @Override()
296  public final String toString()
297  {
298    final StringBuilder buffer = new StringBuilder();
299    toString(buffer);
300    return buffer.toString();
301  }
302
303
304
305  /**
306   * Appends a string representation of this server affinity provider to the
307   * provided buffer.
308   *
309   * @param  buffer  The buffer to which the string representation should be
310   *                 appended.
311   */
312  public abstract void toString(final StringBuilder buffer);
313
314
315
316  /**
317   * {@inheritDoc}
318   */
319  @Override()
320  public Map<List<String>,String> getExamplesArgumentSets()
321  {
322    return Collections.emptyMap();
323  }
324}