|
private ListenableFuture<Void> authenticateV1( |
|
Authenticator authenticator, final ProtocolVersion protocolVersion, final Executor executor) { |
|
Requests.Credentials creds = |
|
new Requests.Credentials(((ProtocolV1Authenticator) authenticator).getCredentials()); |
|
try { |
|
Future authResponseFuture = write(creds); |
|
return Futures.transformAsync( |
|
authResponseFuture, |
|
new AsyncFunction<Message.Response, Void>() { |
|
@Override |
|
public ListenableFuture<Void> apply(Message.Response authResponse) throws Exception { |
|
switch (authResponse.type) { |
|
case READY: |
|
return checkClusterName(protocolVersion, executor); |
|
case ERROR: |
|
incrementAuthErrorMetric(); |
|
throw new AuthenticationException( |
|
endPoint, ((Responses.Error) authResponse).message); |
|
default: |
|
throw new TransportException( |
|
endPoint, |
|
String.format( |
|
"Unexpected %s response message from server to a CREDENTIALS message", |
|
authResponse.type)); |
|
} |
|
} |
|
}, |
|
executor); |
|
} catch (Exception e) { |
|
return Futures.immediateFailedFuture(e); |
|
} |
|
} |
|
|
|
private ListenableFuture<Void> authenticateV2( |
|
final Authenticator authenticator, |
|
final ProtocolVersion protocolVersion, |
|
final Executor executor) { |
|
byte[] initialResponse = authenticator.initialResponse(); |
|
if (null == initialResponse) initialResponse = EMPTY_BYTE_ARRAY; |
|
|
|
try { |
|
Future authResponseFuture = write(new Requests.AuthResponse(initialResponse)); |
|
return Futures.transformAsync( |
|
authResponseFuture, onV2AuthResponse(authenticator, protocolVersion, executor), executor); |
|
} catch (Exception e) { |
|
return Futures.immediateFailedFuture(e); |
|
} |
|
} |
|
|
|
private AsyncFunction<Message.Response, Void> onV2AuthResponse( |
|
final Authenticator authenticator, |
|
final ProtocolVersion protocolVersion, |
|
final Executor executor) { |
|
return new AsyncFunction<Message.Response, Void>() { |
|
@Override |
|
public ListenableFuture<Void> apply(Message.Response authResponse) throws Exception { |
|
switch (authResponse.type) { |
|
case AUTH_SUCCESS: |
|
logger.trace("{} Authentication complete", this); |
|
authenticator.onAuthenticationSuccess(((Responses.AuthSuccess) authResponse).token); |
|
return checkClusterName(protocolVersion, executor); |
|
case AUTH_CHALLENGE: |
|
byte[] responseToServer = |
|
authenticator.evaluateChallenge(((Responses.AuthChallenge) authResponse).token); |
|
if (responseToServer == null) { |
|
// If we generate a null response, then authentication has completed, proceed without |
|
// sending a further response back to the server. |
|
logger.trace("{} Authentication complete (No response to server)", this); |
|
return checkClusterName(protocolVersion, executor); |
|
} else { |
|
// Otherwise, send the challenge response back to the server |
|
logger.trace("{} Sending Auth response to challenge", this); |
|
Future nextResponseFuture = write(new Requests.AuthResponse(responseToServer)); |
|
return Futures.transformAsync( |
|
nextResponseFuture, |
|
onV2AuthResponse(authenticator, protocolVersion, executor), |
|
executor); |
|
} |
|
case ERROR: |
|
// This is not very nice, but we're trying to identify if we |
|
// attempted v2 auth against a server which only supports v1 |
|
// The AIOOBE indicates that the server didn't recognise the |
|
// initial AuthResponse message |
|
String message = ((Responses.Error) authResponse).message; |
|
if (message.startsWith("java.lang.ArrayIndexOutOfBoundsException: 15")) |
|
message = |
|
String.format( |
|
"Cannot use authenticator %s with protocol version 1, " |
|
+ "only plain text authentication is supported with this protocol version", |
|
authenticator); |
|
incrementAuthErrorMetric(); |
|
throw new AuthenticationException(endPoint, message); |
Problem
ScyllaDB will return the native-protocol
OVERLOADEDerror instead ofBAD_CREDENTIALSwhen authentication cannot proceed because the server is overloaded.Java driver 3.x converts every authentication-phase
ERRORresponse intoAuthenticationException, even though its decoder can produceOverloadedException.java-driver/driver-core/src/main/java/com/datastax/driver/core/Connection.java
Lines 698 to 789 in d435e9a
java-driver/driver-core/src/main/java/com/datastax/driver/core/Responses.java
Lines 167 to 180 in d435e9a
The false
AuthenticationExceptionclassification makes a transient overload look like invalid credentials, increments authentication-error metrics, and bypasses normal transient connection handling.Java driver 4.x already handles this condition operationally: only
AUTH_ERRORbecomesAuthenticationException; other setup errors close the incomplete channel and remain eligible for node failover and reconnection.Expected behavior
An
OVERLOADEDresponse toCREDENTIALSorAUTH_RESPONSEpropagates asOverloadedException. The driver discards the incomplete connection and tries other contact points or reconnects according to policy.Acceptance criteria
OverloadedException.BAD_CREDENTIALScontinues to produceAuthenticationException.Related
Jira: https://scylladb.atlassian.net/browse/DRIVER-1121
Parent: https://scylladb.atlassian.net/browse/DRIVER-115