HDFS-4700. Fix the undo section of rename with snapshots. Contributed by Jing Zhao

git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/branches/HDFS-2802@1468632 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Tsz-wo Sze 2013-04-16 22:03:58 +00:00
parent d13f6ebe20
commit 6bda1f20ad
10 changed files with 383 additions and 48 deletions

View File

@ -241,3 +241,6 @@ Branch-2802 Snapshot (Unreleased)
create a file/directory with ".snapshot" as the name. If ".snapshot" is used create a file/directory with ".snapshot" as the name. If ".snapshot" is used
in a previous version of HDFS, it must be renamed before upgrade; otherwise, in a previous version of HDFS, it must be renamed before upgrade; otherwise,
upgrade will fail. (szetszwo) upgrade will fail. (szetszwo)
HDFS-4700. Fix the undo section of rename with snapshots. (Jing Zhao via
szetszwo)

View File

@ -62,6 +62,7 @@ import org.apache.hadoop.hdfs.server.blockmanagement.DatanodeDescriptor;
import org.apache.hadoop.hdfs.server.common.HdfsServerConstants.BlockUCState; import org.apache.hadoop.hdfs.server.common.HdfsServerConstants.BlockUCState;
import org.apache.hadoop.hdfs.server.namenode.INode.BlocksMapUpdateInfo; import org.apache.hadoop.hdfs.server.namenode.INode.BlocksMapUpdateInfo;
import org.apache.hadoop.hdfs.server.namenode.INodeDirectory.INodesInPath; import org.apache.hadoop.hdfs.server.namenode.INodeDirectory.INodesInPath;
import org.apache.hadoop.hdfs.server.namenode.INodeReference.WithCount;
import org.apache.hadoop.hdfs.server.namenode.snapshot.INodeDirectorySnapshottable; import org.apache.hadoop.hdfs.server.namenode.snapshot.INodeDirectorySnapshottable;
import org.apache.hadoop.hdfs.server.namenode.snapshot.INodeDirectoryWithSnapshot; import org.apache.hadoop.hdfs.server.namenode.snapshot.INodeDirectoryWithSnapshot;
import org.apache.hadoop.hdfs.server.namenode.snapshot.Snapshot; import org.apache.hadoop.hdfs.server.namenode.snapshot.Snapshot;
@ -582,12 +583,17 @@ public class FSDirectory implements Closeable {
// check srcChild for reference // check srcChild for reference
final INodeReference.WithCount withCount; final INodeReference.WithCount withCount;
if (srcChildIsReference || isSrcInSnapshot) { int srcRefDstSnapshot = srcChildIsReference ? srcChild.asReference()
.getDstSnapshotId() : Snapshot.INVALID_ID;
if (isSrcInSnapshot) {
final INodeReference.WithName withName = srcIIP.getINode(-2).asDirectory() final INodeReference.WithName withName = srcIIP.getINode(-2).asDirectory()
.replaceChild4ReferenceWithName(srcChild); .replaceChild4ReferenceWithName(srcChild);
withCount = (INodeReference.WithCount)withName.getReferredINode(); withCount = (INodeReference.WithCount) withName.getReferredINode();
srcChild = withName; srcChild = withName;
srcIIP.setLastINode(srcChild); srcIIP.setLastINode(srcChild);
} else if (srcChildIsReference) {
// srcChild is reference but srcChild is not in latest snapshot
withCount = (WithCount) srcChild.asReference().getReferredINode();
} else { } else {
withCount = null; withCount = null;
} }
@ -602,7 +608,6 @@ public class FSDirectory implements Closeable {
return false; return false;
} }
// add src to the destination
if (dstParent.getParent() == null) { if (dstParent.getParent() == null) {
// src and dst file/dir are in the same directory, and the dstParent has // src and dst file/dir are in the same directory, and the dstParent has
// been replaced when we removed the src. Refresh the dstIIP and // been replaced when we removed the src. Refresh the dstIIP and
@ -611,6 +616,8 @@ public class FSDirectory implements Closeable {
dstParent = dstIIP.getINode(-2); dstParent = dstIIP.getINode(-2);
} }
// add src to the destination
srcChild = srcIIP.getLastINode(); srcChild = srcIIP.getLastINode();
final byte[] dstChildName = dstIIP.getLastLocalName(); final byte[] dstChildName = dstIIP.getLastLocalName();
final INode toDst; final INode toDst;
@ -645,17 +652,29 @@ public class FSDirectory implements Closeable {
} }
} finally { } finally {
if (!added) { if (!added) {
final INodeDirectory srcParent = srcIIP.getINode(-2).asDirectory();
final INode oldSrcChild = srcChild;
// put it back // put it back
if (withCount == null) { if (withCount == null) {
srcChild.setLocalName(srcChildName); srcChild.setLocalName(srcChildName);
} else if (!srcChildIsReference) { // src must be in snapshot } else if (!srcChildIsReference) { // src must be in snapshot
final INodeDirectoryWithSnapshot srcParent =
(INodeDirectoryWithSnapshot) srcIIP.getINode(-2).asDirectory();
final INode originalChild = withCount.getReferredINode(); final INode originalChild = withCount.getReferredINode();
srcParent.replaceRemovedChild(srcChild, originalChild);
srcChild = originalChild; srcChild = originalChild;
} else {
final INodeReference originalRef = new INodeReference.DstReference(
srcParent, withCount, srcRefDstSnapshot);
withCount.setParentReference(originalRef);
srcChild = originalRef;
}
if (isSrcInSnapshot) {
((INodeDirectoryWithSnapshot) srcParent).undoRename4ScrParent(
oldSrcChild.asReference(), srcChild, srcIIP.getLatestSnapshot());
} else {
// srcParent is not an INodeDirectoryWithSnapshot, we only need to add
// the srcChild back
addLastINodeNoQuotaCheck(srcIIP, srcChild);
} }
addLastINodeNoQuotaCheck(srcIIP, srcChild);
} }
} }
NameNode.stateChangeLog.warn("DIR* FSDirectory.unprotectedRenameTo: " NameNode.stateChangeLog.warn("DIR* FSDirectory.unprotectedRenameTo: "
@ -797,12 +816,17 @@ public class FSDirectory implements Closeable {
// check srcChild for reference // check srcChild for reference
final INodeReference.WithCount withCount; final INodeReference.WithCount withCount;
if (srcChildIsReference || isSrcInSnapshot) { int srcRefDstSnapshot = srcChildIsReference ? srcChild.asReference()
.getDstSnapshotId() : Snapshot.INVALID_ID;
if (isSrcInSnapshot) {
final INodeReference.WithName withName = srcIIP.getINode(-2).asDirectory() final INodeReference.WithName withName = srcIIP.getINode(-2).asDirectory()
.replaceChild4ReferenceWithName(srcChild); .replaceChild4ReferenceWithName(srcChild);
withCount = (INodeReference.WithCount)withName.getReferredINode(); withCount = (INodeReference.WithCount) withName.getReferredINode();
srcChild = withName; srcChild = withName;
srcIIP.setLastINode(srcChild); srcIIP.setLastINode(srcChild);
} else if (srcChildIsReference) {
// srcChild is reference but srcChild is not in latest snapshot
withCount = (WithCount) srcChild.asReference().getReferredINode();
} else { } else {
withCount = null; withCount = null;
} }
@ -888,21 +912,38 @@ public class FSDirectory implements Closeable {
} finally { } finally {
if (undoRemoveSrc) { if (undoRemoveSrc) {
// Rename failed - restore src // Rename failed - restore src
srcChild = srcIIP.getLastINode(); final INodeDirectory srcParent = srcIIP.getINode(-2).asDirectory();
final INode oldSrcChild = srcChild;
// put it back
if (withCount == null) { if (withCount == null) {
srcChild.setLocalName(srcChildName); srcChild.setLocalName(srcChildName);
} else if (!srcChildIsReference) { // src must be in snapshot } else if (!srcChildIsReference) { // src must be in snapshot
final INodeDirectoryWithSnapshot srcParent
= (INodeDirectoryWithSnapshot)srcIIP.getINode(-2).asDirectory();
final INode originalChild = withCount.getReferredINode(); final INode originalChild = withCount.getReferredINode();
srcParent.replaceRemovedChild(srcChild, originalChild);
srcChild = originalChild; srcChild = originalChild;
} else {
final INodeReference originalRef = new INodeReference.DstReference(
srcParent, withCount, srcRefDstSnapshot);
withCount.setParentReference(originalRef);
srcChild = originalRef;
}
if (srcParent instanceof INodeDirectoryWithSnapshot) {
((INodeDirectoryWithSnapshot) srcParent).undoRename4ScrParent(
oldSrcChild.asReference(), srcChild, srcIIP.getLatestSnapshot());
} else {
// srcParent is not an INodeDirectoryWithSnapshot, we only need to add
// the srcChild back
addLastINodeNoQuotaCheck(srcIIP, srcChild);
} }
addLastINodeNoQuotaCheck(srcIIP, srcChild);
} }
if (undoRemoveDst) { if (undoRemoveDst) {
// Rename failed - restore dst // Rename failed - restore dst
addLastINodeNoQuotaCheck(dstIIP, removedDst); if (dstParent instanceof INodeDirectoryWithSnapshot) {
((INodeDirectoryWithSnapshot) dstParent).undoRename4DstParent(
removedDst, dstIIP.getLatestSnapshot());
} else {
addLastINodeNoQuotaCheck(dstIIP, removedDst);
}
} }
} }
NameNode.stateChangeLog.warn("DIR* FSDirectory.unprotectedRenameTo: " NameNode.stateChangeLog.warn("DIR* FSDirectory.unprotectedRenameTo: "
@ -1270,7 +1311,7 @@ public class FSDirectory implements Closeable {
Quota.Counts counts = targetNode.cleanSubtree(null, latestSnapshot, Quota.Counts counts = targetNode.cleanSubtree(null, latestSnapshot,
collectedBlocks); collectedBlocks);
parent.addSpaceConsumed(-counts.get(Quota.NAMESPACE), parent.addSpaceConsumed(-counts.get(Quota.NAMESPACE),
-counts.get(Quota.DISKSPACE)); -counts.get(Quota.DISKSPACE), true);
removed = counts.get(Quota.NAMESPACE); removed = counts.get(Quota.NAMESPACE);
} }
if (NameNode.stateChangeLog.isDebugEnabled()) { if (NameNode.stateChangeLog.isDebugEnabled()) {

View File

@ -376,11 +376,11 @@ public abstract class INode implements Diff.Element<byte[]> {
* Check and add namespace/diskspace consumed to itself and the ancestors. * Check and add namespace/diskspace consumed to itself and the ancestors.
* @throws QuotaExceededException if quote is violated. * @throws QuotaExceededException if quote is violated.
*/ */
public void addSpaceConsumed(long nsDelta, long dsDelta) public void addSpaceConsumed(long nsDelta, long dsDelta, boolean verify)
throws QuotaExceededException { throws QuotaExceededException {
final INodeDirectory parentDir = getParent(); final INodeDirectory parentDir = getParent();
if (parentDir != null) { if (parentDir != null) {
parentDir.addSpaceConsumed(nsDelta, dsDelta); parentDir.addSpaceConsumed(nsDelta, dsDelta, verify);
} }
} }
@ -403,7 +403,7 @@ public abstract class INode implements Diff.Element<byte[]> {
/** /**
* Count subtree {@link Quota#NAMESPACE} and {@link Quota#DISKSPACE} usages. * Count subtree {@link Quota#NAMESPACE} and {@link Quota#DISKSPACE} usages.
*/ */
final Quota.Counts computeQuotaUsage() { public final Quota.Counts computeQuotaUsage() {
return computeQuotaUsage(new Quota.Counts(), true); return computeQuotaUsage(new Quota.Counts(), true);
} }

View File

@ -216,6 +216,7 @@ public class INodeDirectory extends INodeWithAdditionalFields {
Preconditions.checkState(i >= 0); Preconditions.checkState(i >= 0);
Preconditions.checkState(oldChild == children.get(i)); Preconditions.checkState(oldChild == children.get(i));
// TODO: the first case may never be hit
if (oldChild.isReference() && !newChild.isReference()) { if (oldChild.isReference() && !newChild.isReference()) {
final INode withCount = oldChild.asReference().getReferredINode(); final INode withCount = oldChild.asReference().getReferredINode();
withCount.asReference().setReferredINode(newChild); withCount.asReference().setReferredINode(newChild);

View File

@ -140,21 +140,23 @@ public class INodeDirectoryWithQuota extends INodeDirectory {
} }
@Override @Override
public final void addSpaceConsumed(final long nsDelta, final long dsDelta) public final void addSpaceConsumed(final long nsDelta, final long dsDelta,
throws QuotaExceededException { boolean verify) throws QuotaExceededException {
if (isQuotaSet()) { if (isQuotaSet()) {
// The following steps are important: // The following steps are important:
// check quotas in this inode and all ancestors before changing counts // check quotas in this inode and all ancestors before changing counts
// so that no change is made if there is any quota violation. // so that no change is made if there is any quota violation.
// (1) verify quota in this inode // (1) verify quota in this inode
verifyQuota(nsDelta, dsDelta); if (verify) {
verifyQuota(nsDelta, dsDelta);
}
// (2) verify quota and then add count in ancestors // (2) verify quota and then add count in ancestors
super.addSpaceConsumed(nsDelta, dsDelta); super.addSpaceConsumed(nsDelta, dsDelta, verify);
// (3) add count in this inode // (3) add count in this inode
addSpaceConsumed2Cache(nsDelta, dsDelta); addSpaceConsumed2Cache(nsDelta, dsDelta);
} else { } else {
super.addSpaceConsumed(nsDelta, dsDelta); super.addSpaceConsumed(nsDelta, dsDelta, verify);
} }
} }

View File

@ -254,9 +254,9 @@ public abstract class INodeReference extends INode {
} }
@Override @Override
public final void addSpaceConsumed(long nsDelta, long dsDelta public final void addSpaceConsumed(long nsDelta, long dsDelta, boolean verify)
) throws QuotaExceededException { throws QuotaExceededException {
referred.addSpaceConsumed(nsDelta, dsDelta); referred.addSpaceConsumed(nsDelta, dsDelta, verify);
} }
@Override @Override

View File

@ -108,7 +108,7 @@ abstract class AbstractINodeDiffList<N extends INode,
/** Add an {@link AbstractINodeDiff} for the given snapshot. */ /** Add an {@link AbstractINodeDiff} for the given snapshot. */
final D addDiff(Snapshot latest, N currentINode) final D addDiff(Snapshot latest, N currentINode)
throws QuotaExceededException { throws QuotaExceededException {
currentINode.addSpaceConsumed(1, 0); currentINode.addSpaceConsumed(1, 0, true);
return addLast(factory.createDiff(latest, currentINode)); return addLast(factory.createDiff(latest, currentINode));
} }

View File

@ -325,7 +325,7 @@ public class INodeDirectorySnapshottable extends INodeDirectoryWithSnapshot {
INodeDirectory parent = getParent(); INodeDirectory parent = getParent();
if (parent != null) { if (parent != null) {
parent.addSpaceConsumed(-counts.get(Quota.NAMESPACE), parent.addSpaceConsumed(-counts.get(Quota.NAMESPACE),
-counts.get(Quota.DISKSPACE)); -counts.get(Quota.DISKSPACE), true);
} }
} catch(QuotaExceededException e) { } catch(QuotaExceededException e) {
LOG.error("BUG: removeSnapshot increases namespace usage.", e); LOG.error("BUG: removeSnapshot increases namespace usage.", e);

View File

@ -81,16 +81,16 @@ public class INodeDirectoryWithSnapshot extends INodeDirectoryWithQuota {
return true; return true;
} }
private final boolean removeCreatedChild(final int c, final INode child) { private final boolean removeChild(ListType type, final INode child) {
final List<INode> created = getList(ListType.CREATED); final List<INode> list = getList(type);
if (created.get(c) == child) { final int i = searchIndex(type, child.getLocalNameBytes());
final INode removed = created.remove(c); if (i >= 0 && list.get(i) == child) {
Preconditions.checkState(removed == child); list.remove(i);
return true; return true;
} }
return false; return false;
} }
/** clear the created list */ /** clear the created list */
private Quota.Counts destroyCreatedList( private Quota.Counts destroyCreatedList(
final INodeDirectoryWithSnapshot currentINode, final INodeDirectoryWithSnapshot currentINode,
@ -452,6 +452,18 @@ public class INodeDirectoryWithSnapshot extends INodeDirectoryWithQuota {
} }
return false; return false;
} }
/** Remove the given child in the created/deleted list, if there is any. */
private boolean removeChild(final ListType type, final INode child) {
final List<DirectoryDiff> diffList = asList();
for(int i = diffList.size() - 1; i >= 0; i--) {
final ChildrenDiff diff = diffList.get(i).diff;
if (diff.removeChild(type, child)) {
return true;
}
}
return false;
}
} }
/** /**
@ -627,15 +639,11 @@ public class INodeDirectoryWithSnapshot extends INodeDirectoryWithQuota {
} }
// remove same child from the created list, if there is any. // remove same child from the created list, if there is any.
final byte[] name = child.getLocalNameBytes();
final List<DirectoryDiff> diffList = diffs.asList(); final List<DirectoryDiff> diffList = diffs.asList();
for(int i = diffList.size() - 1; i >= 0; i--) { for(int i = diffList.size() - 1; i >= 0; i--) {
final ChildrenDiff diff = diffList.get(i).diff; final ChildrenDiff diff = diffList.get(i).diff;
final int c = diff.searchIndex(ListType.CREATED, name); if (diff.removeChild(ListType.CREATED, child)) {
if (c >= 0) { return true;
if (diff.removeCreatedChild(c, child)) {
return true;
}
} }
} }
return true; return true;
@ -646,12 +654,65 @@ public class INodeDirectoryWithSnapshot extends INodeDirectoryWithQuota {
super.replaceChild(oldChild, newChild); super.replaceChild(oldChild, newChild);
diffs.replaceChild(ListType.CREATED, oldChild, newChild); diffs.replaceChild(ListType.CREATED, oldChild, newChild);
} }
/** The child just has been removed, replace it with a reference. */ /**
public void replaceRemovedChild(INode oldChild, INode newChild) { * This method is usually called by the undo section of rename.
// the old child must be in the deleted list *
Preconditions.checkState( * Before calling this function, in the rename operation, we replace the
diffs.replaceChild(ListType.DELETED, oldChild, newChild)); * original src node (of the rename operation) with a reference node (WithName
* instance) in both the children list and a created list, delete the
* reference node from the children list, and add it to the corresponding
* deleted list.
*
* To undo the above operations, we have the following steps in particular:
*
* <pre>
* 1) remove the WithName node from the deleted list (if it exists)
* 2) replace the WithName node in the created list with srcChild
* 3) add srcChild back as a child of srcParent. Note that we already add
* the node into the created list of a snapshot diff in step 2, we do not need
* to add srcChild to the created list of the latest snapshot.
* </pre>
*
* We do not need to update quota usage because the old child is in the
* deleted list before.
*
* @param oldChild
* The reference node to be removed/replaced
* @param newChild
* The node to be added back
* @param latestSnapshot
* The latest snapshot. Note this may not be the last snapshot in the
* {@link #diffs}, since the src tree of the current rename operation
* may be the dst tree of a previous rename.
* @throws QuotaExceededException should not throw this exception
*/
public void undoRename4ScrParent(final INodeReference oldChild,
final INode newChild, Snapshot latestSnapshot)
throws QuotaExceededException {
diffs.removeChild(ListType.DELETED, oldChild);
diffs.replaceChild(ListType.CREATED, oldChild, newChild);
addChild(newChild, true, null);
}
/**
* Undo the rename operation for the dst tree, i.e., if the rename operation
* (with OVERWRITE option) removes a file/dir from the dst tree, add it back
* and delete possible record in the deleted list.
*/
public void undoRename4DstParent(final INode deletedChild,
Snapshot latestSnapshot) throws QuotaExceededException {
boolean removeDeletedChild = diffs.removeChild(ListType.DELETED,
deletedChild);
final boolean added = addChild(deletedChild, true, removeDeletedChild ? null
: latestSnapshot);
// update quota usage if adding is successfully and the old child has not
// been stored in deleted list before
if (added && !removeDeletedChild) {
final Quota.Counts counts = deletedChild.computeQuotaUsage();
addSpaceConsumed(counts.get(Quota.NAMESPACE),
counts.get(Quota.DISKSPACE), false);
}
} }
@Override @Override

View File

@ -21,6 +21,10 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@ -48,7 +52,10 @@ import org.apache.hadoop.hdfs.server.namenode.INodeDirectory;
import org.apache.hadoop.hdfs.server.namenode.INodeReference; import org.apache.hadoop.hdfs.server.namenode.INodeReference;
import org.apache.hadoop.hdfs.server.namenode.INodeReference.WithCount; import org.apache.hadoop.hdfs.server.namenode.INodeReference.WithCount;
import org.apache.hadoop.hdfs.server.namenode.snapshot.FileWithSnapshot.FileDiff; import org.apache.hadoop.hdfs.server.namenode.snapshot.FileWithSnapshot.FileDiff;
import org.apache.hadoop.hdfs.server.namenode.snapshot.INodeDirectoryWithSnapshot.ChildrenDiff;
import org.apache.hadoop.hdfs.server.namenode.snapshot.INodeDirectoryWithSnapshot.DirectoryDiff; import org.apache.hadoop.hdfs.server.namenode.snapshot.INodeDirectoryWithSnapshot.DirectoryDiff;
import org.apache.hadoop.hdfs.util.Diff.ListType;
import org.apache.hadoop.hdfs.util.ReadOnlyList;
import org.apache.hadoop.test.GenericTestUtils; import org.apache.hadoop.test.GenericTestUtils;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
@ -1159,4 +1166,224 @@ public class TestRenameWithSnapshots {
hdfs.deleteSnapshot(sdir1, "s1"); hdfs.deleteSnapshot(sdir1, "s1");
restartClusterAndCheckImage(); restartClusterAndCheckImage();
} }
/**
* Test the undo section of rename. Before the rename, we create the renamed
* file/dir before taking the snapshot.
*/
@Test
public void testRenameUndo() throws Exception {
final Path sdir1 = new Path("/dir1");
final Path sdir2 = new Path("/dir2");
hdfs.mkdirs(sdir1);
hdfs.mkdirs(sdir2);
final Path foo = new Path(sdir1, "foo");
final Path bar = new Path(foo, "bar");
DFSTestUtil.createFile(hdfs, bar, BLOCKSIZE, REPL, SEED);
final Path dir2file = new Path(sdir2, "file");
DFSTestUtil.createFile(hdfs, dir2file, BLOCKSIZE, REPL, SEED);
SnapshotTestHelper.createSnapshot(hdfs, sdir1, "s1");
INodeDirectory dir2 = fsdir.getINode4Write(sdir2.toString()).asDirectory();
INodeDirectory mockDir2 = spy(dir2);
doReturn(false).when(mockDir2).addChild((INode) anyObject(), anyBoolean(),
(Snapshot) anyObject());
INodeDirectory root = fsdir.getINode4Write("/").asDirectory();
root.replaceChild(dir2, mockDir2);
final Path newfoo = new Path(sdir2, "foo");
boolean result = hdfs.rename(foo, newfoo);
assertFalse(result);
// check the current internal details
INodeDirectorySnapshottable dir1Node = (INodeDirectorySnapshottable) fsdir
.getINode4Write(sdir1.toString());
ReadOnlyList<INode> dir1Children = dir1Node.getChildrenList(null);
assertEquals(1, dir1Children.size());
assertEquals(foo.getName(), dir1Children.get(0).getLocalName());
List<DirectoryDiff> dir1Diffs = dir1Node.getDiffs().asList();
assertEquals(1, dir1Diffs.size());
assertEquals("s1", dir1Diffs.get(0).snapshot.getRoot().getLocalName());
// after the undo of rename, both the created and deleted list of sdir1
// should be empty
ChildrenDiff childrenDiff = dir1Diffs.get(0).getChildrenDiff();
assertEquals(0, childrenDiff.getList(ListType.DELETED).size());
assertEquals(0, childrenDiff.getList(ListType.CREATED).size());
INode fooNode = fsdir.getINode4Write(foo.toString());
assertTrue(fooNode instanceof INodeDirectoryWithSnapshot);
List<DirectoryDiff> fooDiffs = ((INodeDirectoryWithSnapshot) fooNode)
.getDiffs().asList();
assertEquals(1, fooDiffs.size());
assertEquals("s1", fooDiffs.get(0).snapshot.getRoot().getLocalName());
final Path foo_s1 = SnapshotTestHelper.getSnapshotPath(sdir1, "s1", "foo");
INode fooNode_s1 = fsdir.getINode(foo_s1.toString());
assertTrue(fooNode_s1 == fooNode);
// check sdir2
assertFalse(hdfs.exists(newfoo));
INodeDirectory dir2Node = fsdir.getINode4Write(sdir2.toString())
.asDirectory();
assertFalse(dir2Node instanceof INodeDirectoryWithSnapshot);
ReadOnlyList<INode> dir2Children = dir2Node.getChildrenList(null);
assertEquals(1, dir2Children.size());
assertEquals(dir2file.getName(), dir2Children.get(0).getLocalName());
}
/**
* Test the undo section of rename. Before the rename, we create the renamed
* file/dir after taking the snapshot.
*/
@Test
public void testRenameUndo_2() throws Exception {
final Path sdir1 = new Path("/dir1");
final Path sdir2 = new Path("/dir2");
hdfs.mkdirs(sdir1);
hdfs.mkdirs(sdir2);
final Path dir2file = new Path(sdir2, "file");
DFSTestUtil.createFile(hdfs, dir2file, BLOCKSIZE, REPL, SEED);
SnapshotTestHelper.createSnapshot(hdfs, sdir1, "s1");
// create foo after taking snapshot
final Path foo = new Path(sdir1, "foo");
final Path bar = new Path(foo, "bar");
DFSTestUtil.createFile(hdfs, bar, BLOCKSIZE, REPL, SEED);
INodeDirectory dir2 = fsdir.getINode4Write(sdir2.toString()).asDirectory();
INodeDirectory mockDir2 = spy(dir2);
doReturn(false).when(mockDir2).addChild((INode) anyObject(), anyBoolean(),
(Snapshot) anyObject());
INodeDirectory root = fsdir.getINode4Write("/").asDirectory();
root.replaceChild(dir2, mockDir2);
final Path newfoo = new Path(sdir2, "foo");
boolean result = hdfs.rename(foo, newfoo);
assertFalse(result);
// check the current internal details
INodeDirectorySnapshottable dir1Node = (INodeDirectorySnapshottable) fsdir
.getINode4Write(sdir1.toString());
ReadOnlyList<INode> dir1Children = dir1Node.getChildrenList(null);
assertEquals(1, dir1Children.size());
assertEquals(foo.getName(), dir1Children.get(0).getLocalName());
List<DirectoryDiff> dir1Diffs = dir1Node.getDiffs().asList();
assertEquals(1, dir1Diffs.size());
assertEquals("s1", dir1Diffs.get(0).snapshot.getRoot().getLocalName());
// after the undo of rename, the created list of sdir1 should contain
// 1 element
ChildrenDiff childrenDiff = dir1Diffs.get(0).getChildrenDiff();
assertEquals(0, childrenDiff.getList(ListType.DELETED).size());
assertEquals(1, childrenDiff.getList(ListType.CREATED).size());
INode fooNode = fsdir.getINode4Write(foo.toString());
assertTrue(fooNode instanceof INodeDirectory);
assertTrue(childrenDiff.getList(ListType.CREATED).get(0) == fooNode);
final Path foo_s1 = SnapshotTestHelper.getSnapshotPath(sdir1, "s1", "foo");
assertFalse(hdfs.exists(foo_s1));
// check sdir2
assertFalse(hdfs.exists(newfoo));
INodeDirectory dir2Node = fsdir.getINode4Write(sdir2.toString())
.asDirectory();
assertFalse(dir2Node instanceof INodeDirectoryWithSnapshot);
ReadOnlyList<INode> dir2Children = dir2Node.getChildrenList(null);
assertEquals(1, dir2Children.size());
assertEquals(dir2file.getName(), dir2Children.get(0).getLocalName());
}
/**
* Test the undo section of the second-time rename.
*/
@Test
public void testRenameUndo_3() throws Exception {
final Path sdir1 = new Path("/dir1");
final Path sdir2 = new Path("/dir2");
final Path sdir3 = new Path("/dir3");
hdfs.mkdirs(sdir1);
hdfs.mkdirs(sdir2);
hdfs.mkdirs(sdir3);
final Path foo = new Path(sdir1, "foo");
final Path bar = new Path(foo, "bar");
DFSTestUtil.createFile(hdfs, bar, BLOCKSIZE, REPL, SEED);
SnapshotTestHelper.createSnapshot(hdfs, sdir1, "s1");
SnapshotTestHelper.createSnapshot(hdfs, sdir2, "s2");
INodeDirectory dir3 = fsdir.getINode4Write(sdir3.toString()).asDirectory();
INodeDirectory mockDir3 = spy(dir3);
doReturn(false).when(mockDir3).addChild((INode) anyObject(), anyBoolean(),
(Snapshot) anyObject());
INodeDirectory root = fsdir.getINode4Write("/").asDirectory();
root.replaceChild(dir3, mockDir3);
final Path foo_dir2 = new Path(sdir2, "foo");
final Path foo_dir3 = new Path(sdir3, "foo");
hdfs.rename(foo, foo_dir2);
boolean result = hdfs.rename(foo_dir2, foo_dir3);
assertFalse(result);
// check the current internal details
INodeDirectorySnapshottable dir2Node = (INodeDirectorySnapshottable) fsdir
.getINode4Write(sdir2.toString());
ReadOnlyList<INode> dir2Children = dir2Node.getChildrenList(null);
assertEquals(1, dir2Children.size());
List<DirectoryDiff> dir2Diffs = dir2Node.getDiffs().asList();
assertEquals(1, dir2Diffs.size());
assertEquals("s2", Snapshot.getSnapshotName(dir2Diffs.get(0).snapshot));
ChildrenDiff childrenDiff = dir2Diffs.get(0).getChildrenDiff();
assertEquals(0, childrenDiff.getList(ListType.DELETED).size());
assertEquals(1, childrenDiff.getList(ListType.CREATED).size());
final Path foo_s2 = SnapshotTestHelper.getSnapshotPath(sdir2, "s2", "foo");
assertFalse(hdfs.exists(foo_s2));
INode fooNode = fsdir.getINode4Write(foo_dir2.toString());
assertTrue(childrenDiff.getList(ListType.CREATED).get(0) == fooNode);
assertTrue(fooNode instanceof INodeReference.DstReference);
List<DirectoryDiff> fooDiffs = ((INodeDirectoryWithSnapshot) fooNode
.asDirectory()).getDiffs().asList();
assertEquals(1, fooDiffs.size());
assertEquals("s1", fooDiffs.get(0).snapshot.getRoot().getLocalName());
// create snapshot on sdir2 and rename again
hdfs.createSnapshot(sdir2, "s3");
result = hdfs.rename(foo_dir2, foo_dir3);
assertFalse(result);
// check internal details again
dir2Node = (INodeDirectorySnapshottable) fsdir.getINode4Write(sdir2
.toString());
fooNode = fsdir.getINode4Write(foo_dir2.toString());
dir2Children = dir2Node.getChildrenList(null);
assertEquals(1, dir2Children.size());
dir2Diffs = dir2Node.getDiffs().asList();
assertEquals(2, dir2Diffs.size());
assertEquals("s2", Snapshot.getSnapshotName(dir2Diffs.get(0).snapshot));
assertEquals("s3", Snapshot.getSnapshotName(dir2Diffs.get(1).snapshot));
childrenDiff = dir2Diffs.get(0).getChildrenDiff();
assertEquals(0, childrenDiff.getList(ListType.DELETED).size());
assertEquals(1, childrenDiff.getList(ListType.CREATED).size());
assertTrue(childrenDiff.getList(ListType.CREATED).get(0) == fooNode);
childrenDiff = dir2Diffs.get(1).getChildrenDiff();
assertEquals(0, childrenDiff.getList(ListType.DELETED).size());
assertEquals(0, childrenDiff.getList(ListType.CREATED).size());
final Path foo_s3 = SnapshotTestHelper.getSnapshotPath(sdir2, "s3", "foo");
assertFalse(hdfs.exists(foo_s2));
assertTrue(hdfs.exists(foo_s3));
assertTrue(fooNode instanceof INodeReference.DstReference);
fooDiffs = ((INodeDirectoryWithSnapshot) fooNode.asDirectory()).getDiffs()
.asList();
assertEquals(2, fooDiffs.size());
assertEquals("s1", fooDiffs.get(0).snapshot.getRoot().getLocalName());
assertEquals("s3", fooDiffs.get(1).snapshot.getRoot().getLocalName());
}
} }