/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.fs;
import com.google.common.annotations.VisibleForTesting;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
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.security.token.Token;
import org.apache.hadoop.security.token.TokenIdentifier;
import org.apache.hadoop.util.Time;
/**
* A daemon thread that waits for the next file system to renew.
*/
@InterfaceAudience.Private
public class DelegationTokenRenewer
extends Thread {
private static final Log LOG = LogFactory
.getLog(DelegationTokenRenewer.class);
/** The renewable interface used by the renewer. */
public interface Renewable {
/** @return the renew token. */
public Token<?> getRenewToken();
/** Set delegation token. */
public <T extends TokenIdentifier> void setDelegationToken(Token<T> token);
}
/**
* An action that will renew and replace the file system's delegation
* tokens automatically.
*/
public static class RenewAction<T extends FileSystem & Renewable>
implements Delayed {
/** when should the renew happen */
private long renewalTime;
/** a weak reference to the file system so that it can be garbage collected */
private final WeakReference<T> weakFs;
private Token<?> token;
boolean isValid = true;
private RenewAction(final T fs) {
this.weakFs = new WeakReference<T>(fs);
this.token = fs.getRenewToken();
updateRenewalTime(renewCycle);
}
public boolean isValid() {
return isValid;
}
/** Get the delay until this event should happen. */
@Override
public long getDelay(final TimeUnit unit) {
final long millisLeft = renewalTime - Time.now();
return unit.convert(millisLeft, TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(final Delayed delayed) {
final RenewAction<?> that = (RenewAction<?>)delayed;
return this.renewalTime < that.renewalTime? -1
: this.renewalTime == that.renewalTime? 0: 1;
}
@Override
public int hashCode() {
return token.hashCode();
}
@Override
public boolean equals(final Object that) {
if (this == that) {
return true;
} else if (that == null || !(that instanceof RenewAction)) {
return false;
}
return token.equals(((RenewAction<?>)that).token);
}
/**
* Set a new time for the renewal.
* 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
*/
private void updateRenewalTime(long delay) {
renewalTime = Time.now() + delay - delay/10;
}
/**
* Renew or replace the delegation token for this file system.
* It can only be called when the action is not in the queue.
* @return
* @throws IOException
*/
private boolean renew() throws IOException, InterruptedException {
final T fs = weakFs.get();
final boolean b = fs != null;
if (b) {
synchronized(fs) {
try {
long expires = token.renew(fs.getConf());
updateRenewalTime(expires - Time.now());
} catch (IOException ie) {
try {
Token<?>[] tokens = fs.addDelegationTokens(null, null);
if (tokens.length == 0) {
throw new IOException("addDelegationTokens returned no tokens");
}
token = tokens[0];
updateRenewalTime(renewCycle);
fs.setDelegationToken(token);
} catch (IOException ie2) {
isValid = false;
throw new IOException("Can't renew or get new delegation token ", ie);
}
}
}
}
return b;
}
private void cancel() throws IOException, InterruptedException {
final T fs = weakFs.get();
if (fs != null) {
token.cancel(fs.getConf());
}
}
@Override
public String toString() {
Renewable fs = weakFs.get();
return fs == null? "evaporated token renew"
: "The token will be renewed in " + getDelay(TimeUnit.SECONDS)
+ " secs, renewToken=" + token;
}
}
/** 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;
/** 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();
}
/**
* 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());
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. */
@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
public void run() {
for(;;) {
RenewAction<?> action = null;
try {
action = queue.take();
if (action.renew()) {
queue.add(action);
}
} catch (InterruptedException ie) {
return;
} catch (Exception ie) {
action.weakFs.get().LOG.warn("Failed to renew token, action=" + action,
ie);
}
}
}
}