|
@@ -24,6 +24,8 @@ import java.io.DataInputStream;
|
|
|
import java.io.DataOutputStream;
|
|
|
import java.io.FileNotFoundException;
|
|
|
import java.io.IOException;
|
|
|
+import java.util.HashSet;
|
|
|
+import java.util.Set;
|
|
|
|
|
|
import org.apache.commons.logging.Log;
|
|
|
import org.apache.commons.logging.LogFactory;
|
|
@@ -64,6 +66,7 @@ public class HistoryServerFileSystemStateStoreService
|
|
|
private static final String TOKEN_MASTER_KEY_FILE_PREFIX = "key_";
|
|
|
private static final String TOKEN_FILE_PREFIX = "token_";
|
|
|
private static final String TMP_FILE_PREFIX = "tmp-";
|
|
|
+ private static final String UPDATE_TMP_FILE_PREFIX = "update-";
|
|
|
private static final FsPermission DIR_PERMISSIONS =
|
|
|
new FsPermission((short)0700);
|
|
|
private static final FsPermission FILE_PERMISSIONS = Shell.WINDOWS
|
|
@@ -90,7 +93,7 @@ public class HistoryServerFileSystemStateStoreService
|
|
|
|
|
|
@Override
|
|
|
protected void startStorage() throws IOException {
|
|
|
- fs = rootStatePath.getFileSystem(getConfig());
|
|
|
+ fs = createFileSystem();
|
|
|
createDir(rootStatePath);
|
|
|
tokenStatePath = new Path(rootStatePath, TOKEN_STATE_DIR_NAME);
|
|
|
createDir(tokenStatePath);
|
|
@@ -101,6 +104,10 @@ public class HistoryServerFileSystemStateStoreService
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ FileSystem createFileSystem() throws IOException {
|
|
|
+ return rootStatePath.getFileSystem(getConfig());
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
protected void closeStorage() throws IOException {
|
|
|
// don't close the filesystem as it's part of the filesystem cache
|
|
@@ -127,7 +134,7 @@ public class HistoryServerFileSystemStateStoreService
|
|
|
throw new IOException(tokenPath + " already exists");
|
|
|
}
|
|
|
|
|
|
- createFile(tokenPath, buildTokenData(tokenId, renewDate));
|
|
|
+ createNewFile(tokenPath, buildTokenData(tokenId, renewDate));
|
|
|
}
|
|
|
|
|
|
@Override
|
|
@@ -136,7 +143,25 @@ public class HistoryServerFileSystemStateStoreService
|
|
|
if (LOG.isDebugEnabled()) {
|
|
|
LOG.debug("Updating token " + tokenId.getSequenceNumber());
|
|
|
}
|
|
|
- createFile(getTokenPath(tokenId), buildTokenData(tokenId, renewDate));
|
|
|
+
|
|
|
+ // Files cannot be atomically replaced, therefore we write a temporary
|
|
|
+ // update file, remove the original token file, then rename the update
|
|
|
+ // file to the token file. During recovery either the token file will be
|
|
|
+ // used or if that is missing and an update file is present then the
|
|
|
+ // update file is used.
|
|
|
+ Path tokenPath = getTokenPath(tokenId);
|
|
|
+ Path tmp = new Path(tokenPath.getParent(),
|
|
|
+ UPDATE_TMP_FILE_PREFIX + tokenPath.getName());
|
|
|
+ writeFile(tmp, buildTokenData(tokenId, renewDate));
|
|
|
+ try {
|
|
|
+ deleteFile(tokenPath);
|
|
|
+ } catch (IOException e) {
|
|
|
+ fs.delete(tmp, false);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ if (!fs.rename(tmp, tokenPath)) {
|
|
|
+ throw new IOException("Could not rename " + tmp + " to " + tokenPath);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
@Override
|
|
@@ -168,7 +193,7 @@ public class HistoryServerFileSystemStateStoreService
|
|
|
IOUtils.cleanup(LOG, dataStream);
|
|
|
}
|
|
|
|
|
|
- createFile(keyPath, memStream.toByteArray());
|
|
|
+ createNewFile(keyPath, memStream.toByteArray());
|
|
|
}
|
|
|
|
|
|
@Override
|
|
@@ -213,23 +238,33 @@ public class HistoryServerFileSystemStateStoreService
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private void createFile(Path file, byte[] data) throws IOException {
|
|
|
- final int WRITE_BUFFER_SIZE = 4096;
|
|
|
+ private void createNewFile(Path file, byte[] data)
|
|
|
+ throws IOException {
|
|
|
Path tmp = new Path(file.getParent(), TMP_FILE_PREFIX + file.getName());
|
|
|
- FSDataOutputStream out = fs.create(tmp, FILE_PERMISSIONS, true,
|
|
|
- WRITE_BUFFER_SIZE, fs.getDefaultReplication(tmp),
|
|
|
- fs.getDefaultBlockSize(tmp), null);
|
|
|
+ writeFile(tmp, data);
|
|
|
+ try {
|
|
|
+ if (!fs.rename(tmp, file)) {
|
|
|
+ throw new IOException("Could not rename " + tmp + " to " + file);
|
|
|
+ }
|
|
|
+ } catch (IOException e) {
|
|
|
+ fs.delete(tmp, false);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void writeFile(Path file, byte[] data) throws IOException {
|
|
|
+ final int WRITE_BUFFER_SIZE = 4096;
|
|
|
+ FSDataOutputStream out = fs.create(file, FILE_PERMISSIONS, true,
|
|
|
+ WRITE_BUFFER_SIZE, fs.getDefaultReplication(file),
|
|
|
+ fs.getDefaultBlockSize(file), null);
|
|
|
try {
|
|
|
try {
|
|
|
out.write(data);
|
|
|
} finally {
|
|
|
IOUtils.cleanup(LOG, out);
|
|
|
}
|
|
|
- if (!fs.rename(tmp, file)) {
|
|
|
- throw new IOException("Could not rename " + tmp + " to " + file);
|
|
|
- }
|
|
|
} catch (IOException e) {
|
|
|
- fs.delete(tmp, false);
|
|
|
+ fs.delete(file, false);
|
|
|
throw e;
|
|
|
}
|
|
|
}
|
|
@@ -284,6 +319,19 @@ public class HistoryServerFileSystemStateStoreService
|
|
|
state.tokenMasterKeyState.add(key);
|
|
|
}
|
|
|
|
|
|
+ private void loadTokenFromBucket(int bucketId,
|
|
|
+ HistoryServerState state, Path tokenFile, long numTokenFileBytes)
|
|
|
+ throws IOException {
|
|
|
+ MRDelegationTokenIdentifier token =
|
|
|
+ loadToken(state, tokenFile, numTokenFileBytes);
|
|
|
+ int tokenBucketId = getBucketId(token);
|
|
|
+ if (tokenBucketId != bucketId) {
|
|
|
+ throw new IOException("Token " + tokenFile
|
|
|
+ + " should be in bucket " + tokenBucketId + ", found in bucket "
|
|
|
+ + bucketId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
private MRDelegationTokenIdentifier loadToken(HistoryServerState state,
|
|
|
Path tokenFile, long numTokenFileBytes) throws IOException {
|
|
|
MRDelegationTokenIdentifier tokenId = new MRDelegationTokenIdentifier();
|
|
@@ -308,18 +356,29 @@ public class HistoryServerFileSystemStateStoreService
|
|
|
final int bucketId = Integer.parseInt(numStr);
|
|
|
int numTokens = 0;
|
|
|
FileStatus[] tokenStats = fs.listStatus(bucket);
|
|
|
+ Set<String> loadedTokens = new HashSet<String>(tokenStats.length);
|
|
|
for (FileStatus stat : tokenStats) {
|
|
|
String name = stat.getPath().getName();
|
|
|
if (name.startsWith(TOKEN_FILE_PREFIX)) {
|
|
|
- MRDelegationTokenIdentifier token =
|
|
|
- loadToken(state, stat.getPath(), stat.getLen());
|
|
|
- int tokenBucketId = getBucketId(token);
|
|
|
- if (tokenBucketId != bucketId) {
|
|
|
- throw new IOException("Token " + stat.getPath()
|
|
|
- + " should be in bucket " + tokenBucketId + ", found in bucket "
|
|
|
- + bucketId);
|
|
|
- }
|
|
|
+ loadTokenFromBucket(bucketId, state, stat.getPath(), stat.getLen());
|
|
|
+ loadedTokens.add(name);
|
|
|
++numTokens;
|
|
|
+ } else if (name.startsWith(UPDATE_TMP_FILE_PREFIX)) {
|
|
|
+ String tokenName = name.substring(UPDATE_TMP_FILE_PREFIX.length());
|
|
|
+ if (loadedTokens.contains(tokenName)) {
|
|
|
+ // already have the token, update may be partial so ignore it
|
|
|
+ fs.delete(stat.getPath(), false);
|
|
|
+ } else {
|
|
|
+ // token is missing, so try to parse the update temp file
|
|
|
+ loadTokenFromBucket(bucketId, state, stat.getPath(), stat.getLen());
|
|
|
+ fs.rename(stat.getPath(),
|
|
|
+ new Path(stat.getPath().getParent(), tokenName));
|
|
|
+ loadedTokens.add(tokenName);
|
|
|
+ ++numTokens;
|
|
|
+ }
|
|
|
+ } else if (name.startsWith(TMP_FILE_PREFIX)) {
|
|
|
+ // cleanup incomplete temp files
|
|
|
+ fs.delete(stat.getPath(), false);
|
|
|
} else {
|
|
|
LOG.warn("Skipping unexpected file in history server token bucket: "
|
|
|
+ stat.getPath());
|