|
@@ -18,23 +18,30 @@
|
|
|
|
|
|
package org.apache.hadoop.hdfs.security.token.delegation;
|
|
package org.apache.hadoop.hdfs.security.token.delegation;
|
|
|
|
|
|
|
|
+import com.google.common.annotations.VisibleForTesting;
|
|
|
|
+
|
|
import java.io.IOException;
|
|
import java.io.IOException;
|
|
import java.lang.ref.WeakReference;
|
|
import java.lang.ref.WeakReference;
|
|
import java.util.concurrent.DelayQueue;
|
|
import java.util.concurrent.DelayQueue;
|
|
import java.util.concurrent.Delayed;
|
|
import java.util.concurrent.Delayed;
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
|
|
|
|
+import org.apache.commons.logging.Log;
|
|
|
|
+import org.apache.commons.logging.LogFactory;
|
|
import org.apache.hadoop.classification.InterfaceAudience;
|
|
import org.apache.hadoop.classification.InterfaceAudience;
|
|
-import org.apache.hadoop.fs.FileSystem;
|
|
|
|
import org.apache.hadoop.security.token.Token;
|
|
import org.apache.hadoop.security.token.Token;
|
|
import org.apache.hadoop.security.token.TokenIdentifier;
|
|
import org.apache.hadoop.security.token.TokenIdentifier;
|
|
|
|
+import org.apache.hadoop.fs.FileSystem;
|
|
|
|
|
|
/**
|
|
/**
|
|
* A daemon thread that waits for the next file system to renew.
|
|
* A daemon thread that waits for the next file system to renew.
|
|
*/
|
|
*/
|
|
@InterfaceAudience.Private
|
|
@InterfaceAudience.Private
|
|
-public class DelegationTokenRenewer<T extends FileSystem & DelegationTokenRenewer.Renewable>
|
|
|
|
|
|
+public class DelegationTokenRenewer
|
|
extends Thread {
|
|
extends Thread {
|
|
|
|
+ private static final Log LOG = LogFactory
|
|
|
|
+ .getLog(DelegationTokenRenewer.class);
|
|
|
|
+
|
|
/** The renewable interface used by the renewer. */
|
|
/** The renewable interface used by the renewer. */
|
|
public interface Renewable {
|
|
public interface Renewable {
|
|
/** @return the renew token. */
|
|
/** @return the renew token. */
|
|
@@ -48,18 +55,25 @@ public class DelegationTokenRenewer<T extends FileSystem & DelegationTokenRenewe
|
|
* An action that will renew and replace the file system's delegation
|
|
* An action that will renew and replace the file system's delegation
|
|
* tokens automatically.
|
|
* tokens automatically.
|
|
*/
|
|
*/
|
|
- private static class RenewAction<T extends FileSystem & Renewable>
|
|
|
|
|
|
+ public static class RenewAction<T extends FileSystem & Renewable>
|
|
implements Delayed {
|
|
implements Delayed {
|
|
/** when should the renew happen */
|
|
/** when should the renew happen */
|
|
private long renewalTime;
|
|
private long renewalTime;
|
|
/** a weak reference to the file system so that it can be garbage collected */
|
|
/** a weak reference to the file system so that it can be garbage collected */
|
|
private final WeakReference<T> weakFs;
|
|
private final WeakReference<T> weakFs;
|
|
|
|
+ private Token<?> token;
|
|
|
|
+ boolean isValid = true;
|
|
|
|
|
|
private RenewAction(final T fs) {
|
|
private RenewAction(final T fs) {
|
|
this.weakFs = new WeakReference<T>(fs);
|
|
this.weakFs = new WeakReference<T>(fs);
|
|
- updateRenewalTime();
|
|
|
|
|
|
+ this.token = fs.getRenewToken();
|
|
|
|
+ updateRenewalTime(renewCycle);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ public boolean isValid() {
|
|
|
|
+ return isValid;
|
|
|
|
+ }
|
|
|
|
+
|
|
/** Get the delay until this event should happen. */
|
|
/** Get the delay until this event should happen. */
|
|
@Override
|
|
@Override
|
|
public long getDelay(final TimeUnit unit) {
|
|
public long getDelay(final TimeUnit unit) {
|
|
@@ -76,28 +90,32 @@ public class DelegationTokenRenewer<T extends FileSystem & DelegationTokenRenewe
|
|
|
|
|
|
@Override
|
|
@Override
|
|
public int hashCode() {
|
|
public int hashCode() {
|
|
- return (int)renewalTime ^ (int)(renewalTime >>> 32);
|
|
|
|
|
|
+ return token.hashCode();
|
|
}
|
|
}
|
|
|
|
|
|
@Override
|
|
@Override
|
|
public boolean equals(final Object that) {
|
|
public boolean equals(final Object that) {
|
|
- if (that == null || !(that instanceof RenewAction)) {
|
|
|
|
|
|
+ if (this == that) {
|
|
|
|
+ return true;
|
|
|
|
+ } else if (that == null || !(that instanceof RenewAction)) {
|
|
return false;
|
|
return false;
|
|
}
|
|
}
|
|
- return compareTo((Delayed)that) == 0;
|
|
|
|
|
|
+ return token.equals(((RenewAction<?>)that).token);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
* Set a new time for the renewal.
|
|
* Set a new time for the renewal.
|
|
- * It can only be called when the action is not in the queue.
|
|
|
|
|
|
+ * It can only be called when the action is not in the queue or any
|
|
|
|
+ * collection because the hashCode may change
|
|
* @param newTime the new time
|
|
* @param newTime the new time
|
|
*/
|
|
*/
|
|
- private void updateRenewalTime() {
|
|
|
|
- renewalTime = RENEW_CYCLE + System.currentTimeMillis();
|
|
|
|
|
|
+ private void updateRenewalTime(long delay) {
|
|
|
|
+ renewalTime = System.currentTimeMillis() + delay - delay/10;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
* Renew or replace the delegation token for this file system.
|
|
* Renew or replace the delegation token for this file system.
|
|
|
|
+ * It can only be called when the action is not in the queue.
|
|
* @return
|
|
* @return
|
|
* @throws IOException
|
|
* @throws IOException
|
|
*/
|
|
*/
|
|
@@ -107,15 +125,19 @@ public class DelegationTokenRenewer<T extends FileSystem & DelegationTokenRenewe
|
|
if (b) {
|
|
if (b) {
|
|
synchronized(fs) {
|
|
synchronized(fs) {
|
|
try {
|
|
try {
|
|
- fs.getRenewToken().renew(fs.getConf());
|
|
|
|
|
|
+ long expires = token.renew(fs.getConf());
|
|
|
|
+ updateRenewalTime(expires - System.currentTimeMillis());
|
|
} catch (IOException ie) {
|
|
} catch (IOException ie) {
|
|
try {
|
|
try {
|
|
Token<?>[] tokens = fs.addDelegationTokens(null, null);
|
|
Token<?>[] tokens = fs.addDelegationTokens(null, null);
|
|
if (tokens.length == 0) {
|
|
if (tokens.length == 0) {
|
|
throw new IOException("addDelegationTokens returned no tokens");
|
|
throw new IOException("addDelegationTokens returned no tokens");
|
|
}
|
|
}
|
|
- fs.setDelegationToken(tokens[0]);
|
|
|
|
|
|
+ token = tokens[0];
|
|
|
|
+ updateRenewalTime(renewCycle);
|
|
|
|
+ fs.setDelegationToken(token);
|
|
} catch (IOException ie2) {
|
|
} catch (IOException ie2) {
|
|
|
|
+ isValid = false;
|
|
throw new IOException("Can't renew or get new delegation token ", ie);
|
|
throw new IOException("Can't renew or get new delegation token ", ie);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -124,44 +146,124 @@ public class DelegationTokenRenewer<T extends FileSystem & DelegationTokenRenewe
|
|
return b;
|
|
return b;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ private void cancel() throws IOException, InterruptedException {
|
|
|
|
+ final T fs = weakFs.get();
|
|
|
|
+ if (fs != null) {
|
|
|
|
+ token.cancel(fs.getConf());
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
@Override
|
|
@Override
|
|
public String toString() {
|
|
public String toString() {
|
|
Renewable fs = weakFs.get();
|
|
Renewable fs = weakFs.get();
|
|
return fs == null? "evaporated token renew"
|
|
return fs == null? "evaporated token renew"
|
|
: "The token will be renewed in " + getDelay(TimeUnit.SECONDS)
|
|
: "The token will be renewed in " + getDelay(TimeUnit.SECONDS)
|
|
- + " secs, renewToken=" + fs.getRenewToken();
|
|
|
|
|
|
+ + " secs, renewToken=" + token;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- /** Wait for 95% of a day between renewals */
|
|
|
|
- private static final int RENEW_CYCLE = 24 * 60 * 60 * 950;
|
|
|
|
|
|
+ /** assumes renew cycle for a token is 24 hours... */
|
|
|
|
+ private static final long RENEW_CYCLE = 24 * 60 * 60 * 1000;
|
|
|
|
+
|
|
|
|
+ @InterfaceAudience.Private
|
|
|
|
+ @VisibleForTesting
|
|
|
|
+ public static long renewCycle = RENEW_CYCLE;
|
|
|
|
|
|
- private DelayQueue<RenewAction<T>> queue = new DelayQueue<RenewAction<T>>();
|
|
|
|
|
|
+ /** Queue to maintain the RenewActions to be processed by the {@link #run()} */
|
|
|
|
+ private volatile DelayQueue<RenewAction<?>> queue = new DelayQueue<RenewAction<?>>();
|
|
|
|
+
|
|
|
|
+ /** For testing purposes */
|
|
|
|
+ @VisibleForTesting
|
|
|
|
+ protected int getRenewQueueLength() {
|
|
|
|
+ return queue.size();
|
|
|
|
+ }
|
|
|
|
|
|
- public DelegationTokenRenewer(final Class<T> clazz) {
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Create the singleton instance. However, the thread can be started lazily in
|
|
|
|
+ * {@link #addRenewAction(FileSystem)}
|
|
|
|
+ */
|
|
|
|
+ private static DelegationTokenRenewer INSTANCE = null;
|
|
|
|
+
|
|
|
|
+ private DelegationTokenRenewer(final Class<? extends FileSystem> clazz) {
|
|
super(clazz.getSimpleName() + "-" + DelegationTokenRenewer.class.getSimpleName());
|
|
super(clazz.getSimpleName() + "-" + DelegationTokenRenewer.class.getSimpleName());
|
|
setDaemon(true);
|
|
setDaemon(true);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ public static synchronized DelegationTokenRenewer getInstance() {
|
|
|
|
+ if (INSTANCE == null) {
|
|
|
|
+ INSTANCE = new DelegationTokenRenewer(FileSystem.class);
|
|
|
|
+ }
|
|
|
|
+ return INSTANCE;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @VisibleForTesting
|
|
|
|
+ static synchronized void reset() {
|
|
|
|
+ if (INSTANCE != null) {
|
|
|
|
+ INSTANCE.queue.clear();
|
|
|
|
+ INSTANCE.interrupt();
|
|
|
|
+ try {
|
|
|
|
+ INSTANCE.join();
|
|
|
|
+ } catch (InterruptedException e) {
|
|
|
|
+ LOG.warn("Failed to reset renewer");
|
|
|
|
+ } finally {
|
|
|
|
+ INSTANCE = null;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
/** Add a renew action to the queue. */
|
|
/** Add a renew action to the queue. */
|
|
- public void addRenewAction(final T fs) {
|
|
|
|
- queue.add(new RenewAction<T>(fs));
|
|
|
|
|
|
+ @SuppressWarnings("static-access")
|
|
|
|
+ public <T extends FileSystem & Renewable> RenewAction<T> addRenewAction(final T fs) {
|
|
|
|
+ synchronized (this) {
|
|
|
|
+ if (!isAlive()) {
|
|
|
|
+ start();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ RenewAction<T> action = new RenewAction<T>(fs);
|
|
|
|
+ if (action.token != null) {
|
|
|
|
+ queue.add(action);
|
|
|
|
+ } else {
|
|
|
|
+ fs.LOG.error("does not have a token for renewal");
|
|
|
|
+ }
|
|
|
|
+ return action;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Remove the associated renew action from the queue
|
|
|
|
+ *
|
|
|
|
+ * @throws IOException
|
|
|
|
+ */
|
|
|
|
+ public <T extends FileSystem & Renewable> void removeRenewAction(
|
|
|
|
+ final T fs) throws IOException {
|
|
|
|
+ RenewAction<T> action = new RenewAction<T>(fs);
|
|
|
|
+ if (queue.remove(action)) {
|
|
|
|
+ try {
|
|
|
|
+ action.cancel();
|
|
|
|
+ } catch (InterruptedException ie) {
|
|
|
|
+ LOG.error("Interrupted while canceling token for " + fs.getUri()
|
|
|
|
+ + "filesystem");
|
|
|
|
+ if (LOG.isDebugEnabled()) {
|
|
|
|
+ LOG.debug(ie.getStackTrace());
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ @SuppressWarnings("static-access")
|
|
@Override
|
|
@Override
|
|
public void run() {
|
|
public void run() {
|
|
for(;;) {
|
|
for(;;) {
|
|
- RenewAction<T> action = null;
|
|
|
|
|
|
+ RenewAction<?> action = null;
|
|
try {
|
|
try {
|
|
action = queue.take();
|
|
action = queue.take();
|
|
if (action.renew()) {
|
|
if (action.renew()) {
|
|
- action.updateRenewalTime();
|
|
|
|
queue.add(action);
|
|
queue.add(action);
|
|
}
|
|
}
|
|
} catch (InterruptedException ie) {
|
|
} catch (InterruptedException ie) {
|
|
return;
|
|
return;
|
|
} catch (Exception ie) {
|
|
} catch (Exception ie) {
|
|
- T.LOG.warn("Failed to renew token, action=" + action, ie);
|
|
|
|
|
|
+ action.weakFs.get().LOG.warn("Failed to renew token, action=" + action,
|
|
|
|
+ ie);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|