001 /**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019 package org.apache.hadoop.fs;
020
021 import com.google.common.annotations.VisibleForTesting;
022
023 import java.io.IOException;
024 import java.lang.ref.WeakReference;
025 import java.util.concurrent.DelayQueue;
026 import java.util.concurrent.Delayed;
027 import java.util.concurrent.TimeUnit;
028
029 import org.apache.commons.logging.Log;
030 import org.apache.commons.logging.LogFactory;
031 import org.apache.hadoop.classification.InterfaceAudience;
032 import org.apache.hadoop.security.token.Token;
033 import org.apache.hadoop.security.token.TokenIdentifier;
034 import org.apache.hadoop.util.Time;
035
036 /**
037 * A daemon thread that waits for the next file system to renew.
038 */
039 @InterfaceAudience.Private
040 public class DelegationTokenRenewer
041 extends Thread {
042 private static final Log LOG = LogFactory
043 .getLog(DelegationTokenRenewer.class);
044
045 /** The renewable interface used by the renewer. */
046 public interface Renewable {
047 /** @return the renew token. */
048 public Token<?> getRenewToken();
049
050 /** Set delegation token. */
051 public <T extends TokenIdentifier> void setDelegationToken(Token<T> token);
052 }
053
054 /**
055 * An action that will renew and replace the file system's delegation
056 * tokens automatically.
057 */
058 public static class RenewAction<T extends FileSystem & Renewable>
059 implements Delayed {
060 /** when should the renew happen */
061 private long renewalTime;
062 /** a weak reference to the file system so that it can be garbage collected */
063 private final WeakReference<T> weakFs;
064 private Token<?> token;
065 boolean isValid = true;
066
067 private RenewAction(final T fs) {
068 this.weakFs = new WeakReference<T>(fs);
069 this.token = fs.getRenewToken();
070 updateRenewalTime(renewCycle);
071 }
072
073 public boolean isValid() {
074 return isValid;
075 }
076
077 /** Get the delay until this event should happen. */
078 @Override
079 public long getDelay(final TimeUnit unit) {
080 final long millisLeft = renewalTime - Time.now();
081 return unit.convert(millisLeft, TimeUnit.MILLISECONDS);
082 }
083
084 @Override
085 public int compareTo(final Delayed delayed) {
086 final RenewAction<?> that = (RenewAction<?>)delayed;
087 return this.renewalTime < that.renewalTime? -1
088 : this.renewalTime == that.renewalTime? 0: 1;
089 }
090
091 @Override
092 public int hashCode() {
093 return token.hashCode();
094 }
095
096 @Override
097 public boolean equals(final Object that) {
098 if (this == that) {
099 return true;
100 } else if (that == null || !(that instanceof RenewAction)) {
101 return false;
102 }
103 return token.equals(((RenewAction<?>)that).token);
104 }
105
106 /**
107 * Set a new time for the renewal.
108 * It can only be called when the action is not in the queue or any
109 * collection because the hashCode may change
110 * @param newTime the new time
111 */
112 private void updateRenewalTime(long delay) {
113 renewalTime = Time.now() + delay - delay/10;
114 }
115
116 /**
117 * Renew or replace the delegation token for this file system.
118 * It can only be called when the action is not in the queue.
119 * @return
120 * @throws IOException
121 */
122 private boolean renew() throws IOException, InterruptedException {
123 final T fs = weakFs.get();
124 final boolean b = fs != null;
125 if (b) {
126 synchronized(fs) {
127 try {
128 long expires = token.renew(fs.getConf());
129 updateRenewalTime(expires - Time.now());
130 } catch (IOException ie) {
131 try {
132 Token<?>[] tokens = fs.addDelegationTokens(null, null);
133 if (tokens.length == 0) {
134 throw new IOException("addDelegationTokens returned no tokens");
135 }
136 token = tokens[0];
137 updateRenewalTime(renewCycle);
138 fs.setDelegationToken(token);
139 } catch (IOException ie2) {
140 isValid = false;
141 throw new IOException("Can't renew or get new delegation token ", ie);
142 }
143 }
144 }
145 }
146 return b;
147 }
148
149 private void cancel() throws IOException, InterruptedException {
150 final T fs = weakFs.get();
151 if (fs != null) {
152 token.cancel(fs.getConf());
153 }
154 }
155
156 @Override
157 public String toString() {
158 Renewable fs = weakFs.get();
159 return fs == null? "evaporated token renew"
160 : "The token will be renewed in " + getDelay(TimeUnit.SECONDS)
161 + " secs, renewToken=" + token;
162 }
163 }
164
165 /** assumes renew cycle for a token is 24 hours... */
166 private static final long RENEW_CYCLE = 24 * 60 * 60 * 1000;
167
168 @InterfaceAudience.Private
169 @VisibleForTesting
170 public static long renewCycle = RENEW_CYCLE;
171
172 /** Queue to maintain the RenewActions to be processed by the {@link #run()} */
173 private volatile DelayQueue<RenewAction<?>> queue = new DelayQueue<RenewAction<?>>();
174
175 /** For testing purposes */
176 @VisibleForTesting
177 protected int getRenewQueueLength() {
178 return queue.size();
179 }
180
181 /**
182 * Create the singleton instance. However, the thread can be started lazily in
183 * {@link #addRenewAction(FileSystem)}
184 */
185 private static DelegationTokenRenewer INSTANCE = null;
186
187 private DelegationTokenRenewer(final Class<? extends FileSystem> clazz) {
188 super(clazz.getSimpleName() + "-" + DelegationTokenRenewer.class.getSimpleName());
189 setDaemon(true);
190 }
191
192 public static synchronized DelegationTokenRenewer getInstance() {
193 if (INSTANCE == null) {
194 INSTANCE = new DelegationTokenRenewer(FileSystem.class);
195 }
196 return INSTANCE;
197 }
198
199 @VisibleForTesting
200 static synchronized void reset() {
201 if (INSTANCE != null) {
202 INSTANCE.queue.clear();
203 INSTANCE.interrupt();
204 try {
205 INSTANCE.join();
206 } catch (InterruptedException e) {
207 LOG.warn("Failed to reset renewer");
208 } finally {
209 INSTANCE = null;
210 }
211 }
212 }
213
214 /** Add a renew action to the queue. */
215 @SuppressWarnings("static-access")
216 public <T extends FileSystem & Renewable> RenewAction<T> addRenewAction(final T fs) {
217 synchronized (this) {
218 if (!isAlive()) {
219 start();
220 }
221 }
222 RenewAction<T> action = new RenewAction<T>(fs);
223 if (action.token != null) {
224 queue.add(action);
225 } else {
226 fs.LOG.error("does not have a token for renewal");
227 }
228 return action;
229 }
230
231 /**
232 * Remove the associated renew action from the queue
233 *
234 * @throws IOException
235 */
236 public <T extends FileSystem & Renewable> void removeRenewAction(
237 final T fs) throws IOException {
238 RenewAction<T> action = new RenewAction<T>(fs);
239 if (queue.remove(action)) {
240 try {
241 action.cancel();
242 } catch (InterruptedException ie) {
243 LOG.error("Interrupted while canceling token for " + fs.getUri()
244 + "filesystem");
245 if (LOG.isDebugEnabled()) {
246 LOG.debug(ie.getStackTrace());
247 }
248 }
249 }
250 }
251
252 @SuppressWarnings("static-access")
253 @Override
254 public void run() {
255 for(;;) {
256 RenewAction<?> action = null;
257 try {
258 action = queue.take();
259 if (action.renew()) {
260 queue.add(action);
261 }
262 } catch (InterruptedException ie) {
263 return;
264 } catch (Exception ie) {
265 action.weakFs.get().LOG.warn("Failed to renew token, action=" + action,
266 ie);
267 }
268 }
269 }
270 }