HDFS-7611. deleteSnapshot and delete of a file can leave orphaned blocks in the blocksMap on NameNode restart. Contributed by Jing Zhao and Byron Wong.

This commit is contained in:
Jing Zhao 2015-01-28 15:24:28 -08:00
parent 6d2bdbd7da
commit d244574d03
6 changed files with 71 additions and 21 deletions

View File

@ -807,6 +807,9 @@ Release 2.7.0 - UNRELEASED
HDFS-7677. DistributedFileSystem#truncate should resolve symlinks. (yliu) HDFS-7677. DistributedFileSystem#truncate should resolve symlinks. (yliu)
HDFS-7611. deleteSnapshot and delete of a file can leave orphaned blocks
in the blocksMap on NameNode restart. (jing9 and Byron Wong)
Release 2.6.1 - UNRELEASED Release 2.6.1 - UNRELEASED
INCOMPATIBLE CHANGES INCOMPATIBLE CHANGES

View File

@ -223,20 +223,25 @@ class FSDirDeleteOp {
// set the parent's modification time // set the parent's modification time
final INodeDirectory parent = targetNode.getParent(); final INodeDirectory parent = targetNode.getParent();
parent.updateModificationTime(mtime, latestSnapshot); parent.updateModificationTime(mtime, latestSnapshot);
fsd.updateCountForDelete(targetNode, iip);
if (removed == 0) { if (removed == 0) {
return 0; return 0;
} }
// collect block // collect block and update quota
if (!targetNode.isInLatestSnapshot(latestSnapshot)) { if (!targetNode.isInLatestSnapshot(latestSnapshot)) {
targetNode.destroyAndCollectBlocks(collectedBlocks, removedINodes); targetNode.destroyAndCollectBlocks(collectedBlocks, removedINodes);
} else { } else {
Quota.Counts counts = targetNode.cleanSubtree(CURRENT_STATE_ID, Quota.Counts counts = targetNode.cleanSubtree(CURRENT_STATE_ID,
latestSnapshot, collectedBlocks, removedINodes, true); latestSnapshot, collectedBlocks, removedINodes, true);
parent.addSpaceConsumed(-counts.get(Quota.NAMESPACE),
-counts.get(Quota.DISKSPACE), true);
removed = counts.get(Quota.NAMESPACE); removed = counts.get(Quota.NAMESPACE);
// TODO: quota verification may fail the deletion here. We should not
// count the snapshot diff into quota usage in the future.
fsd.updateCount(iip, -counts.get(Quota.NAMESPACE),
-counts.get(Quota.DISKSPACE), true);
} }
if (NameNode.stateChangeLog.isDebugEnabled()) { if (NameNode.stateChangeLog.isDebugEnabled()) {
NameNode.stateChangeLog.debug("DIR* FSDirectory.unprotectedDelete: " NameNode.stateChangeLog.debug("DIR* FSDirectory.unprotectedDelete: "
+ iip.getPath() + " is removed"); + iip.getPath() + " is removed");

View File

@ -625,9 +625,12 @@ class FSDirRenameOp {
NameNode.stateChangeLog.warn("DIR* FSDirRenameOp.unprotectedRenameTo:" + NameNode.stateChangeLog.warn("DIR* FSDirRenameOp.unprotectedRenameTo:" +
error); error);
throw new IOException(error); throw new IOException(error);
} else {
// update the quota count if necessary
fsd.updateCountForDelete(srcChild, srcIIP);
srcIIP = INodesInPath.replace(srcIIP, srcIIP.length() - 1, null);
return removedNum;
} }
srcIIP = INodesInPath.replace(srcIIP, srcIIP.length() - 1, null);
return removedNum;
} }
boolean removeSrc4OldRename() throws IOException { boolean removeSrc4OldRename() throws IOException {
@ -638,6 +641,8 @@ class FSDirRenameOp {
" can not be removed"); " can not be removed");
return false; return false;
} else { } else {
// update the quota count if necessary
fsd.updateCountForDelete(srcChild, srcIIP);
srcIIP = INodesInPath.replace(srcIIP, srcIIP.length() - 1, null); srcIIP = INodesInPath.replace(srcIIP, srcIIP.length() - 1, null);
return true; return true;
} }
@ -647,6 +652,8 @@ class FSDirRenameOp {
long removedNum = fsd.removeLastINode(dstIIP); long removedNum = fsd.removeLastINode(dstIIP);
if (removedNum != -1) { if (removedNum != -1) {
oldDstChild = dstIIP.getLastINode(); oldDstChild = dstIIP.getLastINode();
// update the quota count if necessary
fsd.updateCountForDelete(oldDstChild, dstIIP);
dstIIP = INodesInPath.replace(dstIIP, dstIIP.length() - 1, null); dstIIP = INodesInPath.replace(dstIIP, dstIIP.length() - 1, null);
} }
return removedNum; return removedNum;

View File

@ -601,7 +601,22 @@ public class FSDirectory implements Closeable {
writeUnlock(); writeUnlock();
} }
} }
/**
* Update the quota usage after deletion. The quota update is only necessary
* when image/edits have been loaded and the file/dir to be deleted is not
* contained in snapshots.
*/
void updateCountForDelete(final INode inode, final INodesInPath iip)
throws QuotaExceededException {
if (getFSNamesystem().isImageLoaded() &&
!inode.isInLatestSnapshot(iip.getLatestSnapshotId())) {
Quota.Counts counts = inode.computeQuotaUsage();
updateCount(iip, -counts.get(Quota.NAMESPACE),
-counts.get(Quota.DISKSPACE), false);
}
}
void updateCount(INodesInPath iip, long nsDelta, long dsDelta, void updateCount(INodesInPath iip, long nsDelta, long dsDelta,
boolean checkQuota) throws QuotaExceededException { boolean checkQuota) throws QuotaExceededException {
updateCount(iip, iip.length() - 1, nsDelta, dsDelta, checkQuota); updateCount(iip, iip.length() - 1, nsDelta, dsDelta, checkQuota);
@ -904,11 +919,12 @@ public class FSDirectory implements Closeable {
/** /**
* Remove the last inode in the path from the namespace. * Remove the last inode in the path from the namespace.
* Count of each ancestor with quota is also updated. * Note: the caller needs to update the ancestors' quota count.
*
* @return -1 for failing to remove; * @return -1 for failing to remove;
* 0 for removing a reference whose referred inode has other * 0 for removing a reference whose referred inode has other
* reference nodes; * reference nodes;
* >0 otherwise. * 1 otherwise.
*/ */
long removeLastINode(final INodesInPath iip) throws QuotaExceededException { long removeLastINode(final INodesInPath iip) throws QuotaExceededException {
final int latestSnapshot = iip.getLatestSnapshotId(); final int latestSnapshot = iip.getLatestSnapshotId();
@ -917,19 +933,9 @@ public class FSDirectory implements Closeable {
if (!parent.removeChild(last, latestSnapshot)) { if (!parent.removeChild(last, latestSnapshot)) {
return -1; return -1;
} }
if (!last.isInLatestSnapshot(latestSnapshot)) {
final Quota.Counts counts = last.computeQuotaUsage();
updateCountNoQuotaCheck(iip, iip.length() - 1,
-counts.get(Quota.NAMESPACE), -counts.get(Quota.DISKSPACE));
if (INodeReference.tryRemoveReference(last) > 0) { return (!last.isInLatestSnapshot(latestSnapshot)
return 0; && INodeReference.tryRemoveReference(last) > 0) ? 0 : 1;
} else {
return counts.get(Quota.NAMESPACE);
}
}
return 1;
} }
static String normalizePath(String src) { static String normalizePath(String src) {

View File

@ -1191,7 +1191,9 @@ public class MiniDFSCluster {
} catch (InterruptedException e) { } catch (InterruptedException e) {
} }
if (++i > 10) { if (++i > 10) {
throw new IOException("Timed out waiting for Mini HDFS Cluster to start"); final String msg = "Timed out waiting for Mini HDFS Cluster to start";
LOG.error(msg);
throw new IOException(msg);
} }
} }
} }

View File

@ -1122,4 +1122,31 @@ public class TestSnapshotDeletion {
// wait till the cluster becomes active // wait till the cluster becomes active
cluster.waitClusterUp(); cluster.waitClusterUp();
} }
@Test
public void testCorrectNumberOfBlocksAfterRestart() throws IOException {
final Path foo = new Path("/foo");
final Path bar = new Path(foo, "bar");
final Path file = new Path(foo, "file");
final String snapshotName = "ss0";
DFSTestUtil.createFile(hdfs, file, BLOCKSIZE, REPLICATION, seed);
hdfs.mkdirs(bar);
hdfs.setQuota(foo, Long.MAX_VALUE - 1, Long.MAX_VALUE - 1);
hdfs.setQuota(bar, Long.MAX_VALUE - 1, Long.MAX_VALUE - 1);
hdfs.allowSnapshot(foo);
hdfs.createSnapshot(foo, snapshotName);
hdfs.setSafeMode(SafeModeAction.SAFEMODE_ENTER);
hdfs.saveNamespace();
hdfs.setSafeMode(SafeModeAction.SAFEMODE_LEAVE);
hdfs.deleteSnapshot(foo, snapshotName);
hdfs.delete(bar, true);
hdfs.delete(foo, true);
long numberOfBlocks = cluster.getNamesystem().getBlocksTotal();
cluster.restartNameNode(0);
assertEquals(numberOfBlocks, cluster.getNamesystem().getBlocksTotal());
}
} }