https://issues.apache.org/jira/browse/HDFS-3083
Early termination with Exception thrown.
In the setting of HA and Security, the use of delegation token result in job failure (e.g., mapreduce job failure, hdfs access failure, etc.). Unable to complete any hdfs jobs. InvalidToken exception is received.
Exceptions from client (from the bug reporter):
12/03/13 06:19:47 INFO mapreduce.Job: map 0% reduce 0%
12/03/13 06:19:47 INFO mapreduce.Job: Job job_1331619043691_0002 failed with state FAILED due to: Application application_1331619043691_0002 failed 1 times due to AM Container for appattempt_1331619043691_0002_000001 exited with exitCode: -1000 due to: RemoteTrace:
org.apache.hadoop.security.token.SecretManager$InvalidToken: token (HDFS_DELEGATION_TOKEN token 40 for yarn) can't be found in cache
at org.apache.hadoop.ipc.Client.call(Client.java:1159)
at org.apache.hadoop.ipc.ProtobufRpcEngine$Invoker.invoke(ProtobufRpcEngine.java:188)
.. .. ..
.Failing this attempt.. Failing the application.
12/03/13 06:19:47 INFO mapreduce.Job: Counters: 0
Job ended: Tue Mar 13 06:19:47 UTC 2012
The job took 3 seconds.
Critical
Yes. See above and Section 3.
Yes. The Failover, and the invalid token exception thrown from NN to client.
Summary:
- Need 2 machines (M1 and M2).
- Set up HA (Hadoop High Availability)
- Set up Security
- $ starting NN (start-dfs.sh) on M1
- $ kinit -k -t key_M1/hdfs.keytab hdfs/test on M1
- $ hdfs haadmin failover nn1 nn2 (so M2 becomes the primary and M1 becomes standby)
- start datanode
- $ hdfs fetchdt --renewer hdfs/test /tmp/token
- $ kdestroy
- $ HADOOP_TOKEN_FILE_LOCATION=/tmp/token hdfs dfs -ls /
---- This command will fail. Any operations accessing hdfs will fail.
Clearly, there are multiple events, and they have to occur in the right sequence.
http://www.cloudera.com/content/cloudera-content/cloudera-docs/CDH4/latest/CDH4-Security-Guide/CDH4-Security-Guide.html
This link is OK reference, but many steps are wrong. Please follow the
steps below.
I used hadoop-2.0.2-alpha, and manually modified the code to reverse the
patch. I tried other versions but they didn't work.
M1: as kerberos server, NFS server NN, and DN
M2: as NFS client, another NN (in hadoop HA)
The Hadoop High Availability (HA) requires 2 NNs on different nodes; it
also requires these 2 NNs to have a shared dir to access. Therefore we
need NFS. There is a gdoc titled "Setting up NFS" to describe this.
http://hadoop.apache.org/docs/r2.0.2-alpha/hadoop-yarn/hadoop-yarn-site/HDFSHighAvailability.html
This is a good link to follow.
Simply use the configuration files I prepared or follow the link above.
No other software needed.
Once you set-up the config files, first format:
hdfs namenode -format
Run this on one machine ONLY (M1), and manually copy the local
namenode dir (specified in dfs.namenode.name.dir) to the other machine
(M2).
Then use the command:
$ start-dfs.sh
then:
$ hdfs haadmin -failover nn1 nn2
You should see failover successful. (note: you should always do a
failover before actual using the hdfs).
I am using 2 NN, and 1 DN in this case.
http://bobthegnome.blogspot.ca/2012/11/testing-kerberos-in-ubuntu.html
Basically just follow this post. There are several important things in
kerberos setting:
The realm (which is a domain): I just named the realm as "TEST".
The principal (user): you can create as many users as you want.
I used kadmin.local command on M1 to manage kerberos.
All right, once you set up the above, you should be good to go!
(and this doesn't have to be a user on your OS!):
$ sudo kadmin.local
kadmin: addprinc -randkey hdfs/test@TEST
kadmin: xst -norandkey -k etc/hadoop/key/hdfs.keytab hdfs/test@TEST
The above will generate the keytab file for hdfs/test@TEST. Copy this
file to both M1 and M2, and set the configuration entry
"dfs.namenode.keytab.file"
"dfs.secondary.namenode.keytab.file"
"dfs.datanode.keytab.file"
to point to the keytab files.
should be successful:
M1$ kinit -k -t key_M1/hdfs.keytab hdfs/test on M1
M2$ kinit -k -t key_M2/hdfs.keytab hdfs/test
Commands below are all to be executed on M1:
M1$ klist
--- This should print some info about your tgt info.
M1$ start-dfs.sh
M1$ hdfs haadmin failover nn1 nn2 (so M2 becomes the primary and M1 becomes standby)
M1$ sudo -s
M1$ hadoop-daemon.sh start datanode
--- execute on M1:
M1$ hdfs dfs -ls /
$ hdfs dfs -put ./somefile /
$ hdfs dfs -ls /
--- "somefile" should show up.
Commands below are to be executed M1, unless otherwise stated.
$ starting NN (start-dfs.sh) on M1
$ kinit -k -t key_M1/hdfs.keytab hdfs/test on M1
M2$ kinit -k -t key_M2/hdfs.keytab hdfs/test on M2
$ hdfs haadmin failover nn1 nn2 (so M2 becomes the primary and M1 becomes standby)
$ sudo -s
$ hadoop-daemon.sh start datanode
$ hdfs fetchdt --renewer hdfs/haha /tmp/token
$ kdestroy
$ HADOOP_TOKEN_FILE_LOCATION=/tmp/token hdfs dfs -ls /
--- It should fail with:
13/07/04 11:52:32 ERROR security.UserGroupInformation: PriviledgedActionException as:ding (auth:KERBEROS) cause:org.apache.hadoop.ipc.RemoteException(org.apache.hadoop.security.token.SecretManager$InvalidToken): token (HDFS_DELEGATION_TOKEN token 3 for hdfs) can't be found in cache
13/07/04 11:52:32 WARN ipc.Client: Exception encountered while connecting to the server : org.apache.hadoop.ipc.RemoteException(org.apache.hadoop.security.token.SecretManager$InvalidToken): token (HDFS_DELEGATION_TOKEN token 3 for hdfs) can't be found in cache
without showing the ls result. The correct behavior, though the client
log will also show some ERROR msgs, should show the ls result.
To stop:
$ stop-dfs.sh
$ sudo -s
$ hadoop-daemon.sh stop datanode
This timing order is externally visible.
Yes!
Two machines.
When client execute:
M1:~/research/hadoop/hadoop-2.0.2-alpha-src/hadoop-dist/target/hadoop-2.0.2-alpha/etc/hadoop$ HADOOP_TOKEN_FILE_LOCATION=/tmp/token hdfs dfs -ls /
13/07/04 11:52:12 INFO hdfs.DFSClient: Created HDFS_DELEGATION_TOKEN token 3 for hdfs on ha-hdfs:mycluster
13/07/04 11:52:32 ERROR security.UserGroupInformation: PriviledgedActionException as:ding (auth:KERBEROS) cause:org.apache.hadoop.ipc.RemoteException(org.apache.hadoop.security.token.SecretManager$InvalidToken): token (HDFS_DELEGATION_TOKEN token 3 for hdfs) can't be found in cache
13/07/04 11:52:32 WARN ipc.Client: Exception encountered while connecting to the server : org.apache.hadoop.ipc.RemoteException(org.apache.hadoop.security.token.SecretManager$InvalidToken): token (HDFS_DELEGATION_TOKEN token 3 for hdfs) can't be found in cache
13/07/04 11:52:32 ERROR security.UserGroupInformation: PriviledgedActionException as:ding (auth:KERBEROS) cause:org.apache.hadoop.ipc.RemoteException(org.apache.hadoop.security.token.SecretManager$InvalidToken): token (HDFS_DELEGATION_TOKEN token 3 for hdfs) can't be found in cache
It does not show the expected output (should show the ls result).
HADOOP_TOKEN_FILE_LOCATION=/tmp/token hdfs dfs -ls /
13/07/04 08:43:43 ERROR security.UserGroupInformation: PriviledgedActionException as:ding (auth:KERBEROS) cause:org.apache.hadoop.ipc.RemoteException(org.apache.hadoop.ipc.StandbyException): Operation category READ is not supported in state standby
13/07/04 08:43:43 WARN ipc.Client: Exception encountered while connecting to the server : org.apache.hadoop.ipc.RemoteException(org.apache.hadoop.ipc.StandbyException): Operation category READ is not supported in state standby
13/07/04 08:43:43 ERROR security.UserGroupInformation: PriviledgedActionException as:ding (auth:KERBEROS) cause:org.apache.hadoop.ipc.RemoteException(org.apache.hadoop.ipc.StandbyException): Operation category READ is not supported in state standby
In fact, the diagnosis of this failure is not too complicated.
"RemoteException: SecretManager$InvalidToken.
(it is sent from NN).
2013-07-04 11:52:32,221 WARN SecurityLogger.org.apache.hadoop.ipc.Server: Auth failed for 128.100.23.4:60160:null
2013-07-04 11:52:32,222 INFO org.apache.hadoop.ipc.Server: IPC Server listener on 8020: readAndProcess threw exception javax.security.sasl.SaslException: DIGEST-MD5: IO error acquiring password [Caused by org.apache.hadoop.security.token.SecretManager$InvalidToken: token (HDFS_DELEGATION_TOKEN token 3 for hdfs) can't be found in cache] from client 128.100.23.4. Count of bytes read: 0
javax.security.sasl.SaslException: DIGEST-MD5: IO error acquiring password [Caused by org.apache.hadoop.security.token.SecretManager$InvalidToken: token (HDFS_DELEGATION_TOKEN token 3 for hdfs) can't be found in cache]
at com.sun.security.sasl.digest.DigestMD5Server.validateClientResponse(DigestMD5Server.java:577)
at com.sun.security.sasl.digest.DigestMD5Server.evaluateResponse(DigestMD5Server.java:226)
at org.apache.hadoop.ipc.Server$Connection.saslReadAndProcess(Server.java:1203)
at org.apache.hadoop.ipc.Server$Connection.readAndProcess(Server.java:1397)
at org.apache.hadoop.ipc.Server$Listener.doRead(Server.java:712)
.. .. ..
Note this is from M1--- the standby NN.
If we look at the active NN (M2):
2013-07-04 17:10:36,612 DEBUG org.apache.hadoop.ipc.Server: Have read input token of size 75 for processing by saslServer.unwrap()
2013-07-04 17:10:36,613 INFO SecurityLogger.org.apache.hadoop.security.authorize.ServiceAuthorizationManager: Authorization successful for hdfs/haha@ (auth:KERBEROS) for protocol=interface org.apache.hadoop.hdfs.server.protocol.DatanodeProtocol
2013-07-04 17:10:36,614 DEBUG org.apache.hadoop.ipc.Server: Successfully authorized userInfo {
effectiveUser: "hdfs/haha@"
}
protocol: "org.apache.hadoop.hdfs.server.protocol.DatanodeProtocol"
--- seems log-in successful!
Why this exception resulted in client termination?
By only looking at the client code, it is hard to know where the
exception is received. The exception was thrown by NN, so we cannot find
any specific text in the client. But a programmer familiar with client
code should be able to reason about how the client handles the
exception.
If we enable the DEBUG log on client, we can clearly see how client
handles the exception via a call stack:
13/07/04 11:52:32 DEBUG ipc.Client: closing ipc connection to baker221.eecg.utoronto.ca/128.100.23.4:8020: token (HDFS_DELEGATION_TOKEN token 3 for hdfs) can't be found in cache
org.apache.hadoop.ipc.RemoteException(org.apache.hadoop.security.token.SecretManager$InvalidToken): token (HDFS_DELEGATION_TOKEN token 3 for hdfs) can't be found in cache
at org.apache.hadoop.security.SaslRpcClient.readStatus(SaslRpcClient.java:111)
at org.apache.hadoop.security.SaslRpcClient.saslConnect(SaslRpcClient.java:173)
at org.apache.hadoop.ipc.Client$Connection.setupSaslConnection(Client.java:425)
at org.apache.hadoop.ipc.Client$Connection.access$1300(Client.java:220)
at org.apache.hadoop.ipc.Client$Connection$2.run(Client.java:592)
at org.apache.hadoop.ipc.Client$Connection$2.run(Client.java:589)
at java.security.AccessController.doPrivileged(Native Method)
at javax.security.auth.Subject.doAs(Subject.java:396)
at org.apache.hadoop.security.UserGroupInformation.doAs(UserGroupInformation.java:1367)
at org.apache.hadoop.ipc.Client$Connection.setupIOstreams(Client.java:588)
at org.apache.hadoop.ipc.Client$Connection.access$1700(Client.java:220)
at org.apache.hadoop.ipc.Client.getConnection(Client.java:1217)
at org.apache.hadoop.ipc.Client.call(Client.java:1144)
at org.apache.hadoop.ipc.ProtobufRpcEngine$Invoker.invoke(ProtobufRpcEngine.java:202)
at com.sun.proxy.$Proxy9.getFileInfo(Unknown Source)
at org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolTranslatorPB.getFileInfo(ClientNamenodeProtocolTranslatorPB.java:608)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.apache.hadoop.io.retry.RetryInvocationHandler.invokeMethod(RetryInvocationHandler.java:164)
at org.apache.hadoop.io.retry.RetryInvocationHandler.invoke(RetryInvocationHandler.java:83)
at com.sun.proxy.$Proxy10.getFileInfo(Unknown Source)
.. .. ..
If we look at the source of "invoke" in (RetryInvocationHandler.java):
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
while (true) {
// The number of times this invocation handler has ever been failed over,
// before this method invocation attempt. Used to prevent concurrent
// failed method invocations from triggering multiple failover attempts.
long invocationAttemptFailoverCount;
synchronized (proxyProvider) {
invocationAttemptFailoverCount = proxyProviderFailoverCount;
}
try {
Object ret = invokeMethod(method, args);
hasMadeASuccessfulCall = true;
return ret;
} catch (Exception e) {
LOG.debug("Ding: REtryInvocationHandler.java: invoke got exception", e);
boolean isMethodIdempotent = proxyProvider.getInterface()
.getMethod(method.getName(), method.getParameterTypes())
.isAnnotationPresent(Idempotent.class);
RetryAction action = policy.shouldRetry(e, retries++, invocationFailoverCount,
isMethodIdempotent);
<--- For this particular exception (InvalidToken), client
decides it should NOT retry (should retry decision is FAIL). And the
reason is "null".
if (action.action == RetryAction.RetryDecision.FAIL) {
if (action.reason != null) {
LOG.warn("Exception while invoking " +
currentProxy.getClass() + "." + method.getName() +
". Not retrying because " + action.reason, e);
}
throw e;
} else { // retry or failover
// avoid logging the failover if this is the first call on this
// proxy object, and we successfully achieve the failover without
// any flip-flopping
boolean worthLogging =
!(invocationFailoverCount == 0 && !hasMadeASuccessfulCall);
worthLogging |= LOG.isDebugEnabled();
if (action.action == RetryAction.RetryDecision.FAILOVER_AND_RETRY &&
worthLogging) {
String msg = "Exception while invoking " + method.getName()
+ " of class " + currentProxy.getClass().getSimpleName();
if (invocationFailoverCount > 0) {
msg += " after " + invocationFailoverCount + " fail over attempts";
}
msg += ". Trying to fail over " + formatSleepMessage(action.delayMillis);
if (LOG.isDebugEnabled()) {
LOG.debug(msg, e);
} else {
LOG.warn(msg);
}
} else {
if(LOG.isDebugEnabled()) {
LOG.debug("Exception while invoking " + method.getName()
+ " of class " + currentProxy.getClass().getSimpleName() +
". Retrying " + formatSleepMessage(action.delayMillis), e);
}
}
if (action.delayMillis > 0) {
LOG.debug("Ding: RetryInvocationHandler.java: sleepATLeastIgnoreInterrupt");
ThreadUtil.sleepAtLeastIgnoreInterrupts(action.delayMillis);
}
if (action.action == RetryAction.RetryDecision.FAILOVER_AND_RETRY) {
// Make sure that concurrent failed method invocations only cause a
// single actual fail over.
synchronized (proxyProvider) {
if (invocationAttemptFailoverCount == proxyProviderFailoverCount) {
proxyProvider.performFailover(currentProxy);
proxyProviderFailoverCount++;
currentProxy = proxyProvider.getProxy();
} else {
LOG.warn("A failover has occurred since the start of this method"
+ " invocation attempt.");
}
}
invocationFailoverCount++;
}
}
}
}
}
Now, let's look at the NN's code to see why this InvalidToken is thrown:
private void saslReadAndProcess(byte[] saslToken) throws IOException,
InterruptedException {
if (!saslContextEstablished) {
byte[] replyToken = null;
try {
if (saslServer == null) {
switch (authMethod) {
case DIGEST:
if (secretManager == null) {
throw new AccessControlException(
"Server is not configured to do DIGEST authentication.");
}
/* secretManager.checkAvailableForRead(); */ // Ding
saslServer = Sasl.createSaslServer(AuthMethod.DIGEST
.getMechanismName(), null, SaslRpcServer.SASL_DEFAULT_REALM,
SaslRpcServer.SASL_PROPS, new SaslDigestCallbackHandler(
secretManager, this));
break;
default:
.. ..
}
if (LOG.isDebugEnabled())
LOG.debug("Have read input token of size " + saslToken.length
+ " for processing by saslServer.evaluateResponse()");
replyToken = saslServer.evaluateResponse(saslToken);
} catch (IOException e) {
IOException sendToClient = e;
Throwable cause = e;
while (cause != null) {
if (cause instanceof InvalidToken) {
sendToClient = (InvalidToken) cause;
break;
}
cause = cause.getCause();
}
doSaslReply(SaslStatus.ERROR, null, sendToClient.getClass().getName(),
sendToClient.getLocalizedMessage());
rpcMetrics.incrAuthenticationFailures();
String clientIP = this.toString();
// attempting user could be null
AUDITLOG.warn(AUTH_FAILED_FOR + clientIP + ":" + attemptingUser);
throw e;
}
.. ..
So createSaslServer will throw an exception (InvalidToken), and fall into the "catch" block above, which will further send back to the client.
If we really think about it, the fact this standby NN throws InvalidToken exception is really not surprising:
This token was generated by the active NN (M2), and cannot be verified
by the active NN.
Here is the sequence of event that are relevant:
A). Client got a delegation token from the NNs (active NN generated this token)
B). Client used this token (and not tgt because we used kdestroy) to do an hdfs command (-ls).
C). Active NN (M2) accepted this token, however, standby NN (M1)
rejects this token (because it was generated by M2). It throws
InvalidToken back to client.
D). Client handles InvalidToken in a way that will terminate the
execution immidiately without retrying.
E). The fix: letting M1 throw an exception other than InvalidToken, so client will retry on that exception!
Incorrect exception handling. This is a tough one to test. Statement coverage won’t work. YOu have to anticipate the error scenario to test it.