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}