You are viewing a plain text version of this content. The canonical link for it is here.
Posted to hdfs-commits@hadoop.apache.org by su...@apache.org on 2012/10/31 04:13:21 UTC

svn commit: r1403959 - in /hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src: main/java/org/apache/hadoop/hdfs/protocol/ main/java/org/apache/hadoop/hdfs/server/namenode/ main/java/org/apache/hadoop/hdfs/server/namenode/snapshot/ tes...

Author: suresh
Date: Wed Oct 31 03:13:20 2012
New Revision: 1403959

URL: http://svn.apache.org/viewvc?rev=1403959&view=rev
Log:
HDFS-4118. Change INodeDirectory.getExistingPathINodes(..) to work with snapshots. Contributed by Jing Zhao.

Added:
    hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestSnapshot.java
Removed:
    hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/snapshot/TestSnapshot.java
Modified:
    hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/protocol/HdfsConstants.java
    hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirectory.java
    hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/INode.java
    hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/INodeDirectory.java
    hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/snapshot/INodeDirectorySnapshottable.java
    hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/snapshot/INodeFileWithLink.java
    hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/snapshot/SnapshotManager.java

Modified: hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/protocol/HdfsConstants.java
URL: http://svn.apache.org/viewvc/hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/protocol/HdfsConstants.java?rev=1403959&r1=1403958&r2=1403959&view=diff
==============================================================================
--- hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/protocol/HdfsConstants.java (original)
+++ hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/protocol/HdfsConstants.java Wed Oct 31 03:13:20 2012
@@ -102,4 +102,9 @@ public class HdfsConstants {
    */
   public static final int LAYOUT_VERSION = LayoutVersion
       .getCurrentLayoutVersion();
+  
+  /**
+   * A special path component contained in the path for a snapshot file/dir
+   */
+  public static final String DOT_SNAPSHOT_DIR = ".snapshot";
 }

Modified: hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirectory.java
URL: http://svn.apache.org/viewvc/hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirectory.java?rev=1403959&r1=1403958&r2=1403959&view=diff
==============================================================================
--- hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirectory.java (original)
+++ hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirectory.java Wed Oct 31 03:13:20 2012
@@ -300,50 +300,6 @@ public class FSDirectory implements Clos
     return newNode;
   }
 
-  /** Add an INodeFileSnapshot to the source file. */
-  INodeFileSnapshot addFileSnapshot(String srcPath, String dstPath
-      ) throws IOException, QuotaExceededException {
-    waitForReady();
-
-    final INodeFile src = INodeFile.valueOf(rootDir.getNode(srcPath, false), srcPath);
-    INodeFileSnapshot snapshot = new INodeFileSnapshot(src, src.computeFileSize(true)); 
-
-    writeLock();
-    try {
-      //add destination snaplink
-      snapshot = addNode(dstPath, snapshot, UNKNOWN_DISK_SPACE);
-
-      final INodeFileWithLink srcWithLink;
-      if (snapshot != null) {
-        //added snapshot node successfully, check source type,
-        if (src instanceof INodeFileWithLink) {
-          srcWithLink = (INodeFileWithLink)src;
-        } else {
-          //source is an INodeFile, replace the source.
-          srcWithLink = new INodeFileWithLink(src);
-          replaceNode(srcPath, src, srcWithLink);
-        }
-        
-        //insert the snapshot to src's linked list.
-        srcWithLink.insert(snapshot);
-      }
-    } finally {
-      writeUnlock();
-
-      if (snapshot == null) {
-        NameNode.stateChangeLog.info(
-            "DIR* FSDirectory.addFileSnapshot: failed to add " + dstPath);
-        return null;
-      }
-    }
-
-    if (NameNode.stateChangeLog.isDebugEnabled()) {
-      NameNode.stateChangeLog.debug("DIR* FSDirectory.addFileSnapshot: "
-          + dstPath + " is added to the file system");
-    }
-    return snapshot;
-  }
-
   INodeDirectory addToParent(byte[] src, INodeDirectory parentINode,
       INode newNode, boolean propagateModTime) {
     // NOTE: This does not update space counts for parents

Modified: hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/INode.java
URL: http://svn.apache.org/viewvc/hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/INode.java?rev=1403959&r1=1403958&r2=1403959&view=diff
==============================================================================
--- hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/INode.java (original)
+++ hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/INode.java Wed Oct 31 03:13:20 2012
@@ -228,7 +228,7 @@ public abstract class INode implements C
    * Get local file name
    * @return local file name
    */
-  String getLocalName() {
+  public String getLocalName() {
     return DFSUtil.bytes2String(name);
   }
 
@@ -280,6 +280,11 @@ public abstract class INode implements C
     return this.parent;
   }
 
+  /** Set parent directory */
+  public void setParent(INodeDirectory parent) {
+    this.parent = parent;
+  }
+  
   /** 
    * Get last modification time of inode.
    * @return access time

Modified: hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/INodeDirectory.java
URL: http://svn.apache.org/viewvc/hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/INodeDirectory.java?rev=1403959&r1=1403958&r2=1403959&view=diff
==============================================================================
--- hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/INodeDirectory.java (original)
+++ hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/INodeDirectory.java Wed Oct 31 03:13:20 2012
@@ -27,7 +27,10 @@ import org.apache.hadoop.fs.UnresolvedLi
 import org.apache.hadoop.fs.permission.PermissionStatus;
 import org.apache.hadoop.hdfs.DFSUtil;
 import org.apache.hadoop.hdfs.protocol.Block;
+import org.apache.hadoop.hdfs.protocol.HdfsConstants;
 import org.apache.hadoop.hdfs.protocol.UnresolvedPathException;
+import org.apache.hadoop.hdfs.server.namenode.snapshot.INodeDirectorySnapshotRoot;
+import org.apache.hadoop.hdfs.server.namenode.snapshot.INodeDirectorySnapshottable;
 
 /**
  * Directory INode class.
@@ -190,9 +193,8 @@ public class INodeDirectory extends INod
    *        be thrown when the path refers to a symbolic link.
    * @return the specified number of existing INodes in the path
    */
-  INodesInPath getExistingPathINodes(byte[][] components, int numOfINodes,
-      boolean resolveLink)
-      throws UnresolvedLinkException {
+  INodesInPath getExistingPathINodes(byte[][] components, int numOfINodes, 
+      boolean resolveLink) throws UnresolvedLinkException {
     assert this.compareTo(components[0]) == 0 :
         "Incorrect name " + getLocalName() + " expected "
         + (components[0] == null? null: DFSUtil.bytes2String(components[0]));
@@ -207,7 +209,7 @@ public class INodeDirectory extends INod
     while (count < components.length && curNode != null) {
       final boolean lastComp = (count == components.length - 1);      
       if (index >= 0) {
-        existing.inodes[index] = curNode;
+        existing.addNode(curNode);
       }
       if (curNode.isLink() && (!lastComp || (lastComp && resolveLink))) {
         final String path = constructPath(components, 0, components.length);
@@ -228,7 +230,31 @@ public class INodeDirectory extends INod
         break;
       }
       INodeDirectory parentDir = (INodeDirectory)curNode;
-      curNode = parentDir.getChildINode(components[count + 1]);
+      
+      // check if the next byte[] in components is for ".snapshot"
+      if (isDotSnapshotDir(components[count + 1])
+          && (curNode instanceof INodeDirectorySnapshottable)) {
+        // skip the ".snapshot" in components
+        count++;
+        index++;
+        existing.isSnapshot = true;
+        if (index >= 0) { // decrease the capacity by 1 to account for .snapshot
+          existing.capacity--;
+        }
+        // check if ".snapshot" is the last element of components
+        if (count == components.length - 1) {
+          return existing;
+        }
+        // Resolve snapshot root
+        curNode = ((INodeDirectorySnapshottable) parentDir)
+            .getSnapshotINode(components[count + 1]);
+        if (index >= -1) {
+          existing.snapshotRootIndex = existing.size;
+        }
+      } else {
+        // normal case, and also for resolving file/dir under snapshot root
+        curNode = parentDir.getChildINode(components[count + 1]);
+      }
       count++;
       index++;
     }
@@ -236,6 +262,14 @@ public class INodeDirectory extends INod
   }
 
   /**
+   * @return true if path component is {@link HdfsConstants#DOT_SNAPSHOT_DIR}
+   */
+  private static boolean isDotSnapshotDir(byte[] pathComponent) {
+    return pathComponent == null ? false : HdfsConstants.DOT_SNAPSHOT_DIR
+        .equalsIgnoreCase(DFSUtil.bytes2String(pathComponent));
+  }
+  
+  /**
    * Retrieve the existing INodes along the given path. The first INode
    * always exist and is this INode.
    * 
@@ -455,18 +489,83 @@ public class INodeDirectory extends INod
   /**
    * Used by
    * {@link INodeDirectory#getExistingPathINodes(byte[][], int, boolean)}.
-   * Containing INodes information resolved from a given path.
+   * Contains INodes information resolved from a given path.
    */
   static class INodesInPath {
+    /**
+     * Array with the specified number of INodes resolved for a given path.
+     */
     private INode[] inodes;
-    
-    public INodesInPath(int number) {
+    /**
+     * Indicate the number of non-null elements in {@link #inodes}
+     */
+    private int size;
+    /**
+     * The path for a snapshot file/dir contains the .snapshot thus makes the
+     * length of the path components larger the number of inodes. We use
+     * the capacity to control this special case.
+     */
+    private int capacity;
+    /**
+     * true if this path corresponds to a snapshot
+     */
+    private boolean isSnapshot;
+    /**
+     * Index of {@link INodeDirectorySnapshotRoot} for snapshot path, else -1
+     */
+    private int snapshotRootIndex;
+
+    INodesInPath(int number) {
       assert (number >= 0);
-      this.inodes = new INode[number];
+      inodes = new INode[number];
+      capacity = number;
+      size = 0;
+      isSnapshot = false;
+      snapshotRootIndex = -1;
     }
-    
+
+    /**
+     * @return the whole inodes array including the null elements.
+     */
     INode[] getINodes() {
+      if (capacity < inodes.length) {
+        INode[] newNodes = new INode[capacity];
+        for (int i = 0; i < capacity; i++) {
+          newNodes[i] = inodes[i];
+        }
+        inodes = newNodes;
+      }
       return inodes;
     }
+    
+    /**
+     * @return index of the {@link INodeDirectorySnapshotRoot} in
+     *         {@link #inodes} for snapshot path, else -1.
+     */
+    int getSnapshotRootIndex() {
+      return this.snapshotRootIndex;
+    }
+    
+    /**
+     * @return isSnapshot true for a snapshot path
+     */
+    boolean isSnapshot() {
+      return this.isSnapshot;
+    }
+    
+    /**
+     * Add an INode at the end of the array
+     */
+    private void addNode(INode node) {
+      assert size < inodes.length;
+      inodes[size++] = node;
+    }
+    
+    /**
+     * @return The number of non-null elements
+     */
+    int getSize() {
+      return size;
+    }
   }
 }

Modified: hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/snapshot/INodeDirectorySnapshottable.java
URL: http://svn.apache.org/viewvc/hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/snapshot/INodeDirectorySnapshottable.java?rev=1403959&r1=1403958&r2=1403959&view=diff
==============================================================================
--- hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/snapshot/INodeDirectorySnapshottable.java (original)
+++ hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/snapshot/INodeDirectorySnapshottable.java Wed Oct 31 03:13:20 2012
@@ -19,6 +19,7 @@ package org.apache.hadoop.hdfs.server.na
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 import org.apache.hadoop.HadoopIllegalArgumentException;
@@ -57,6 +58,18 @@ public class INodeDirectorySnapshottable
   /** A list of snapshots of this directory. */
   private final List<INodeDirectorySnapshotRoot> snapshots
       = new ArrayList<INodeDirectorySnapshotRoot>();
+  
+  public INode getSnapshotINode(byte[] name) {
+    if (snapshots == null || snapshots.size() == 0) {
+      return null;
+    }
+    int low = Collections.binarySearch(snapshots, name);
+    if (low >= 0) {
+      return snapshots.get(low);
+    }
+    return null;
+  }
+  
   /** Number of snapshots is allowed. */
   private int snapshotQuota;
 

Modified: hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/snapshot/INodeFileWithLink.java
URL: http://svn.apache.org/viewvc/hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/snapshot/INodeFileWithLink.java?rev=1403959&r1=1403958&r2=1403959&view=diff
==============================================================================
--- hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/snapshot/INodeFileWithLink.java (original)
+++ hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/snapshot/INodeFileWithLink.java Wed Oct 31 03:13:20 2012
@@ -37,6 +37,7 @@ public class INodeFileWithLink extends I
 
   public INodeFileWithLink(INodeFile f) {
     super(f);
+    setLocalName(f.getLocalName());
     next = this;
   }
 

Modified: hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/snapshot/SnapshotManager.java
URL: http://svn.apache.org/viewvc/hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/snapshot/SnapshotManager.java?rev=1403959&r1=1403958&r2=1403959&view=diff
==============================================================================
--- hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/snapshot/SnapshotManager.java (original)
+++ hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/snapshot/SnapshotManager.java Wed Oct 31 03:13:20 2012
@@ -109,7 +109,7 @@ public class SnapshotManager {
       final List<INode> children = srcDir.getChildren();
       if (children != null) {
         final List<INode> inodes = new ArrayList<INode>(children.size());
-        for(final INode c : children) {
+        for(final INode c : new ArrayList<INode>(children)) {
           final INode i;
           if (c == null) {
             i = null;
@@ -127,6 +127,7 @@ public class SnapshotManager {
             throw new AssertionError("Unknow INode type: " + c.getClass()
                 + ", inode = " + c);
           }
+          i.setParent(dstDir);
           inodes.add(i);
         }
         dstDir.setChildren(inodes);

Added: hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestSnapshot.java
URL: http://svn.apache.org/viewvc/hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestSnapshot.java?rev=1403959&view=auto
==============================================================================
--- hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestSnapshot.java (added)
+++ hadoop/common/branches/HDFS-2802/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestSnapshot.java Wed Oct 31 03:13:20 2012
@@ -0,0 +1,394 @@
+/**
+ * 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.hdfs.server.namenode;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.hdfs.DFSTestUtil;
+import org.apache.hadoop.hdfs.DistributedFileSystem;
+import org.apache.hadoop.hdfs.MiniDFSCluster;
+import org.apache.hadoop.hdfs.server.namenode.INodeDirectory.INodesInPath;
+import org.apache.hadoop.hdfs.server.namenode.snapshot.INodeDirectorySnapshotRoot;
+import org.apache.hadoop.hdfs.server.namenode.snapshot.INodeDirectorySnapshottable;
+import org.apache.hadoop.hdfs.server.namenode.snapshot.INodeFileSnapshot;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+/** Test snapshot related operations. */
+public class TestSnapshot {
+  private static final long seed = 0;
+  private static final short REPLICATION = 3;
+
+  private final Path dir = new Path("/TestSnapshot");
+  
+  private final Path sub1 = new Path(dir, "sub1");
+  private final Path file1 = new Path(sub1, "file1");
+  private final Path file2 = new Path(sub1, "file2");
+
+  private Configuration conf;
+  private MiniDFSCluster cluster;
+  private FSNamesystem fsn;
+  private FSDirectory fsdir;
+
+  private DistributedFileSystem hdfs;
+
+  @Before
+  public void setUp() throws Exception {
+    conf = new Configuration();
+    cluster = new MiniDFSCluster.Builder(conf)
+      .numDataNodes(REPLICATION)
+      .build();
+    cluster.waitActive();
+    
+    fsn = cluster.getNamesystem();
+    fsdir = fsn.getFSDirectory();
+    
+    hdfs = cluster.getFileSystem();
+    DFSTestUtil.createFile(hdfs, file1, 1024, REPLICATION, seed);
+    DFSTestUtil.createFile(hdfs, file2, 1024, REPLICATION, seed);
+  }
+
+  @After
+  public void tearDown() throws Exception {
+    if (cluster != null) {
+      cluster.shutdown();
+    }
+  }
+
+  /** Test allow-snapshot operation. */
+  @Test
+  public void testAllowSnapshot() throws Exception {
+    final String path = sub1.toString();
+    final INode before = fsdir.getINode(path);
+    
+    // Before a directory is snapshottable
+    Assert.assertTrue(before instanceof INodeDirectory);
+    Assert.assertFalse(before instanceof INodeDirectorySnapshottable);
+
+    // After a directory is snapshottable
+    hdfs.allowSnapshot(path);
+    final INode after = fsdir.getINode(path);
+    Assert.assertTrue(after instanceof INodeDirectorySnapshottable);
+  }
+  
+  /** 
+   * Test {@link INodeDirectory#getExistingPathINodes(byte[][], int, boolean)} 
+   * for normal (non-snapshot) file.
+   */
+  @Test
+  public void testNonSnapshotPathINodes() throws Exception {
+    // Get the inodes by resolving the path of a normal file
+    String[] names = INode.getPathNames(file1.toString());
+    byte[][] components = INode.getPathComponents(names);
+    INodesInPath nodesInPath = fsdir.rootDir.getExistingPathINodes(components,
+        components.length, false);
+    INode[] inodes = nodesInPath.getINodes();
+    // The number of inodes should be equal to components.length
+    assertEquals(inodes.length, components.length);
+    // The returned nodesInPath should be non-snapshot
+    assertFalse(nodesInPath.isSnapshot());
+    assertEquals(nodesInPath.getSnapshotRootIndex(), -1);
+    // The last INode should be associated with file1
+    assertEquals(inodes[components.length - 1].getFullPathName(),
+        file1.toString());
+    assertEquals(inodes[components.length - 2].getFullPathName(),
+        sub1.toString());
+    assertEquals(inodes[components.length - 3].getFullPathName(),
+        dir.toString());
+    
+    // Call getExistingPathINodes and request only one INode. This is used
+    // when identifying the INode for a given path.
+    nodesInPath = fsdir.rootDir.getExistingPathINodes(components, 1, false);
+    inodes = nodesInPath.getINodes();
+    assertEquals(inodes.length, 1);
+    assertFalse(nodesInPath.isSnapshot());
+    assertEquals(nodesInPath.getSnapshotRootIndex(), -1);
+    assertEquals(inodes[0].getFullPathName(), file1.toString());
+    
+    // Call getExistingPathINodes and request 2 INodes. This is usually used
+    // when identifying the parent INode of a given path.
+    nodesInPath = fsdir.rootDir.getExistingPathINodes(components, 2, false);
+    inodes = nodesInPath.getINodes();
+    assertEquals(inodes.length, 2);
+    assertFalse(nodesInPath.isSnapshot());
+    assertEquals(nodesInPath.getSnapshotRootIndex(), -1);
+    assertEquals(inodes[1].getFullPathName(), file1.toString());
+    assertEquals(inodes[0].getFullPathName(), sub1.toString());
+  }
+  
+  /** 
+   * Test {@link INodeDirectory#getExistingPathINodes(byte[][], int, boolean)} 
+   * for snapshot file.
+   */
+  @Test
+  public void testSnapshotPathINodes() throws Exception {
+    // Create a snapshot for the dir, and check the inodes for the path
+    // pointing to a snapshot file
+    hdfs.allowSnapshot(sub1.toString());
+    hdfs.createSnapshot("s1", sub1.toString());
+    // The path when accessing the snapshot file of file1 is
+    // /TestSnapshot/sub1/.snapshot/s1/file1
+    String snapshotPath = sub1.toString() + "/.snapshot/s1/file1";
+    String[] names = INode.getPathNames(snapshotPath);
+    byte[][] components = INode.getPathComponents(names);
+    INodesInPath nodesInPath = fsdir.rootDir.getExistingPathINodes(components,
+        components.length, false);
+    INode[] inodes = nodesInPath.getINodes();
+    // Length of inodes should be (components.length - 1), since we will ignore
+    // ".snapshot" 
+    assertEquals(inodes.length, components.length - 1);
+    assertTrue(nodesInPath.isSnapshot());
+    // SnapshotRootIndex should be 3: {root, Testsnapshot, sub1, s1, file1}
+    assertEquals(nodesInPath.getSnapshotRootIndex(), 3);
+    assertTrue(inodes[nodesInPath.getSnapshotRootIndex()] instanceof 
+        INodeDirectorySnapshotRoot);
+    // Check the INode for file1 (snapshot file)
+    INode snapshotFileNode = inodes[inodes.length - 1]; 
+    assertEquals(snapshotFileNode.getLocalName(), file1.getName());
+    assertTrue(snapshotFileNode instanceof INodeFileSnapshot);
+    assertTrue(snapshotFileNode.getParent() instanceof 
+        INodeDirectorySnapshotRoot);
+    
+    // Call getExistingPathINodes and request only one INode.
+    nodesInPath = fsdir.rootDir.getExistingPathINodes(components, 1, false);
+    inodes = nodesInPath.getINodes();
+    assertEquals(inodes.length, 1);
+    assertTrue(nodesInPath.isSnapshot());
+    // The snapshotroot (s1) is not included in inodes. Thus the
+    // snapshotRootIndex should be -1.
+    assertEquals(nodesInPath.getSnapshotRootIndex(), -1);
+    // Check the INode for file1 (snapshot file)
+    snapshotFileNode = inodes[inodes.length - 1]; 
+    assertEquals(snapshotFileNode.getLocalName(), file1.getName());
+    assertTrue(snapshotFileNode instanceof INodeFileSnapshot);
+    
+    // Call getExistingPathINodes and request 2 INodes.
+    nodesInPath = fsdir.rootDir.getExistingPathINodes(components, 2, false);
+    inodes = nodesInPath.getINodes();
+    assertEquals(inodes.length, 2);
+    assertTrue(nodesInPath.isSnapshot());
+    // There should be two INodes in inodes: s1 and snapshot of file1. Thus the
+    // SnapshotRootIndex should be 0.
+    assertEquals(nodesInPath.getSnapshotRootIndex(), 0);
+    snapshotFileNode = inodes[inodes.length - 1];
+    // Check the INode for snapshot of file1
+    assertEquals(snapshotFileNode.getLocalName(), file1.getName());
+    assertTrue(snapshotFileNode instanceof INodeFileSnapshot);
+    
+    // Resolve the path "/TestSnapshot/sub1/.snapshot"  
+    String dotSnapshotPath = sub1.toString() + "/.snapshot";
+    names = INode.getPathNames(dotSnapshotPath);
+    components = INode.getPathComponents(names);
+    nodesInPath = fsdir.rootDir.getExistingPathINodes(components,
+        components.length, false);
+    inodes = nodesInPath.getINodes();
+    // The number of INodes returned should be components.length - 1 since we
+    // will ignore ".snapshot"
+    assertEquals(inodes.length, components.length - 1);
+    assertTrue(nodesInPath.isSnapshot());
+    // No SnapshotRoot dir is included in the resolved inodes  
+    assertEquals(nodesInPath.getSnapshotRootIndex(), -1);
+    // The last INode should be the INode for sub1
+    assertEquals(inodes[inodes.length - 1].getFullPathName(), sub1.toString());
+    assertFalse(inodes[inodes.length - 1] instanceof INodeFileSnapshot);
+  }
+  
+  /** 
+   * Test {@link INodeDirectory#getExistingPathINodes(byte[][], int, boolean)} 
+   * for snapshot file after deleting the original file.
+   */
+  @Test
+  public void testSnapshotPathINodesAfterDeletion() throws Exception {
+    // Create a snapshot for the dir, and check the inodes for the path
+    // pointing to a snapshot file
+    hdfs.allowSnapshot(sub1.toString());
+    hdfs.createSnapshot("s1", sub1.toString());
+    
+    // Delete the original file /TestSnapshot/sub1/file1
+    hdfs.delete(file1, false);
+    
+    // Check the INodes for path /TestSnapshot/sub1/file1
+    String[] names = INode.getPathNames(file1.toString());
+    byte[][] components = INode.getPathComponents(names);
+    INodesInPath nodesInPath = fsdir.rootDir.getExistingPathINodes(components,
+        components.length, false);
+    INode[] inodes = nodesInPath.getINodes();
+    // The length of inodes should be equal to components.length
+    assertEquals(inodes.length, components.length);
+    // The number of non-null elements should be components.length - 1 since
+    // file1 has been deleted
+    assertEquals(nodesInPath.getSize(), components.length - 1);
+    // The returned nodesInPath should be non-snapshot
+    assertFalse(nodesInPath.isSnapshot());
+    assertEquals(nodesInPath.getSnapshotRootIndex(), -1);
+    // The last INode should be null, and the one before should be associated
+    // with sub1
+    assertNull(inodes[components.length - 1]);
+    assertEquals(inodes[components.length - 2].getFullPathName(),
+        sub1.toString());
+    assertEquals(inodes[components.length - 3].getFullPathName(),
+        dir.toString());
+    
+    // Resolve the path for the snapshot file
+    // /TestSnapshot/sub1/.snapshot/s1/file1
+    String snapshotPath = sub1.toString() + "/.snapshot/s1/file1";
+    names = INode.getPathNames(snapshotPath);
+    components = INode.getPathComponents(names);
+    nodesInPath = fsdir.rootDir.getExistingPathINodes(components,
+        components.length, false);
+    inodes = nodesInPath.getINodes();
+    // Length of inodes should be (components.length - 1), since we will ignore
+    // ".snapshot" 
+    assertEquals(inodes.length, components.length - 1);
+    assertTrue(nodesInPath.isSnapshot());
+    // SnapshotRootIndex should be 3: {root, Testsnapshot, sub1, s1, file1}
+    assertEquals(nodesInPath.getSnapshotRootIndex(), 3);
+    assertTrue(inodes[nodesInPath.getSnapshotRootIndex()] instanceof 
+        INodeDirectorySnapshotRoot);
+    // Check the INode for file1 (snapshot file)
+    INode snapshotFileNode = inodes[inodes.length - 1]; 
+    assertEquals(snapshotFileNode.getLocalName(), file1.getName());
+    assertTrue(snapshotFileNode instanceof INodeFileSnapshot);
+    assertTrue(snapshotFileNode.getParent() instanceof 
+        INodeDirectorySnapshotRoot);
+  }
+  
+  /** 
+   * Test {@link INodeDirectory#getExistingPathINodes(byte[][], int, boolean)} 
+   * for snapshot file while adding a new file after snapshot.
+   */
+  @Test
+  public void testSnapshotPathINodesWithAddedFile() throws Exception {
+    // Create a snapshot for the dir, and check the inodes for the path
+    // pointing to a snapshot file
+    hdfs.allowSnapshot(sub1.toString());
+    hdfs.createSnapshot("s1", sub1.toString());
+    
+    // Add a new file /TestSnapshot/sub1/file3
+    final Path file3 = new Path(sub1, "file3");
+    DFSTestUtil.createFile(hdfs, file3, 1024, REPLICATION, seed);
+  
+    // Check the inodes for /TestSnapshot/sub1/file3
+    String[] names = INode.getPathNames(file3.toString());
+    byte[][] components = INode.getPathComponents(names);
+    INodesInPath nodesInPath = fsdir.rootDir.getExistingPathINodes(components,
+        components.length, false);
+    INode[] inodes = nodesInPath.getINodes();
+    // The number of inodes should be equal to components.length
+    assertEquals(inodes.length, components.length);
+    // The returned nodesInPath should be non-snapshot
+    assertFalse(nodesInPath.isSnapshot());
+    assertEquals(nodesInPath.getSnapshotRootIndex(), -1);
+    // The last INode should be associated with file3
+    assertEquals(inodes[components.length - 1].getFullPathName(),
+        file3.toString());
+    assertEquals(inodes[components.length - 2].getFullPathName(),
+        sub1.toString());
+    assertEquals(inodes[components.length - 3].getFullPathName(),
+        dir.toString());
+    
+    // Check the inodes for /TestSnapshot/sub1/.snapshot/s1/file3
+    String snapshotPath = sub1.toString() + "/.snapshot/s1/file3";
+    names = INode.getPathNames(snapshotPath);
+    components = INode.getPathComponents(names);
+    nodesInPath = fsdir.rootDir.getExistingPathINodes(components,
+        components.length, false);
+    inodes = nodesInPath.getINodes();
+    // Length of inodes should be (components.length - 1), since we will ignore
+    // ".snapshot" 
+    assertEquals(inodes.length, components.length - 1);
+    // The number of non-null inodes should be components.length - 2, since
+    // snapshot of file3 does not exist
+    assertEquals(nodesInPath.getSize(), components.length - 2);
+    assertTrue(nodesInPath.isSnapshot());
+    // SnapshotRootIndex should still be 3: {root, Testsnapshot, sub1, s1, null}
+    assertEquals(nodesInPath.getSnapshotRootIndex(), 3);
+    assertTrue(inodes[nodesInPath.getSnapshotRootIndex()] instanceof 
+        INodeDirectorySnapshotRoot);
+    // Check the last INode in inodes, which should be null
+    assertNull(inodes[inodes.length - 1]);
+    assertTrue(inodes[inodes.length - 2] instanceof 
+        INodeDirectorySnapshotRoot);
+  }
+  
+  /** 
+   * Test {@link INodeDirectory#getExistingPathINodes(byte[][], int, boolean)} 
+   * for snapshot file while modifying file after snapshot.
+   */
+  @Test
+  public void testSnapshotPathINodesAfterModification() throws Exception {
+    // First check the INode for /TestSnapshot/sub1/file1
+    String[] names = INode.getPathNames(file1.toString());
+    byte[][] components = INode.getPathComponents(names);
+    INodesInPath nodesInPath = fsdir.rootDir.getExistingPathINodes(components,
+        components.length, false);
+    INode[] inodes = nodesInPath.getINodes();
+    // The number of inodes should be equal to components.length
+    assertEquals(inodes.length, components.length);
+    // The last INode should be associated with file1
+    assertEquals(inodes[components.length - 1].getFullPathName(),
+        file1.toString());
+    
+    // Create a snapshot for the dir, and check the inodes for the path
+    // pointing to a snapshot file
+    hdfs.allowSnapshot(sub1.toString());
+    hdfs.createSnapshot("s1", sub1.toString());
+    
+    // Modify file1
+    DFSTestUtil.appendFile(hdfs, file1, "the content for appending");
+    // Check the INode for /TestSnapshot/sub1/file1 again
+    INodesInPath newNodesInPath = fsdir.rootDir
+        .getExistingPathINodes(components, components.length, false);
+    INode[] newInodes = newNodesInPath.getINodes();
+    // The number of inodes should be equal to components.length
+    assertEquals(newInodes.length, components.length);
+    // The last INode should be associated with file1
+    assertEquals(newInodes[components.length - 1].getFullPathName(),
+        file1.toString());
+    // The modification time of the INode for file3 should have been changed
+    Assert.assertFalse(inodes[components.length - 1].getModificationTime() ==
+        newInodes[components.length - 1].getModificationTime());
+    
+    // Check the INodes for snapshot of file1
+    String snapshotPath = sub1.toString() + "/.snapshot/s1/file1";
+    names = INode.getPathNames(snapshotPath);
+    components = INode.getPathComponents(names);
+    INodesInPath ssNodesInPath = fsdir.rootDir.getExistingPathINodes(
+        components, components.length, false);
+    INode[] ssInodes = ssNodesInPath.getINodes();
+    // Length of ssInodes should be (components.length - 1), since we will
+    // ignore ".snapshot" 
+    assertEquals(ssInodes.length, components.length - 1);
+    assertTrue(ssNodesInPath.isSnapshot());
+    // Check the INode for snapshot of file1
+    INode snapshotFileNode = ssInodes[ssInodes.length - 1]; 
+    assertEquals(snapshotFileNode.getLocalName(), file1.getName());
+    assertTrue(snapshotFileNode instanceof INodeFileSnapshot);
+    // The modification time of the snapshot INode should be the same with the
+    // original INode before modification
+    assertEquals(inodes[inodes.length - 1].getModificationTime(),
+        ssInodes[ssInodes.length - 1].getModificationTime());
+  }
+}