blob: b62a4180d43ba45255a81e5006b62d13027152b2 [file] [log] [blame]
/**
* 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.assertNull;
import static org.junit.Assert.assertTrue;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hdfs.DFSTestUtil;
import org.apache.hadoop.hdfs.DFSUtil;
import org.apache.hadoop.hdfs.DistributedFileSystem;
import org.apache.hadoop.hdfs.MiniDFSCluster;
import org.apache.hadoop.hdfs.protocol.SnapshotException;
import org.apache.hadoop.hdfs.server.namenode.snapshot.Snapshot;
import org.apache.hadoop.hdfs.server.namenode.snapshot.SnapshotManager;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.Mockito;
/** Test snapshot related operations. */
public class TestSnapshotPathINodes {
private static final long seed = 0;
private static final short REPLICATION = 3;
static private final Path dir = new Path("/TestSnapshot");
static private final Path sub1 = new Path(dir, "sub1");
static private final Path file1 = new Path(sub1, "file1");
static private final Path file2 = new Path(sub1, "file2");
static private MiniDFSCluster cluster;
static private FSDirectory fsdir;
static private DistributedFileSystem hdfs;
@BeforeClass
public static void setUp() throws Exception {
Configuration conf = new Configuration();
cluster = new MiniDFSCluster.Builder(conf)
.numDataNodes(REPLICATION)
.build();
cluster.waitActive();
FSNamesystem fsn = cluster.getNamesystem();
fsdir = fsn.getFSDirectory();
hdfs = cluster.getFileSystem();
}
@Before
public void reset() throws Exception {
DFSTestUtil.createFile(hdfs, file1, 1024, REPLICATION, seed);
DFSTestUtil.createFile(hdfs, file2, 1024, REPLICATION, seed);
}
@AfterClass
public static void tearDown() throws Exception {
if (cluster != null) {
cluster.shutdown();
}
}
/** Test allow-snapshot operation. */
@Test (timeout=15000)
public void testAllowSnapshot() throws Exception {
final String pathStr = sub1.toString();
final INode before = fsdir.getINode(pathStr);
// Before a directory is snapshottable
Assert.assertFalse(before.asDirectory().isSnapshottable());
// After a directory is snapshottable
final Path path = new Path(pathStr);
hdfs.allowSnapshot(path);
{
final INode after = fsdir.getINode(pathStr);
Assert.assertTrue(after.asDirectory().isSnapshottable());
}
hdfs.disallowSnapshot(path);
{
final INode after = fsdir.getINode(pathStr);
Assert.assertFalse(after.asDirectory().isSnapshottable());
}
}
static Snapshot getSnapshot(INodesInPath inodesInPath, String name,
int index) {
if (name == null) {
return null;
}
final INode inode = inodesInPath.getINode(index - 1);
return inode.asDirectory().getSnapshot(DFSUtil.string2Bytes(name));
}
static void assertSnapshot(INodesInPath inodesInPath, boolean isSnapshot,
final Snapshot snapshot, int index) {
assertEquals(isSnapshot, inodesInPath.isSnapshot());
assertEquals(Snapshot.getSnapshotId(isSnapshot ? snapshot : null),
inodesInPath.getPathSnapshotId());
if (!isSnapshot) {
assertEquals(Snapshot.getSnapshotId(snapshot),
inodesInPath.getLatestSnapshotId());
}
if (isSnapshot && index >= 0) {
assertEquals(Snapshot.Root.class, inodesInPath.getINode(index).getClass());
}
}
static void assertINodeFile(INode inode, Path path) {
assertEquals(path.getName(), inode.getLocalName());
assertEquals(INodeFile.class, inode.getClass());
}
/**
* for normal (non-snapshot) file.
*/
@Test (timeout=15000)
public void testNonSnapshotPathINodes() throws Exception {
// Get the inodes by resolving the path of a normal file
byte[][] components = INode.getPathComponents(file1.toString());
INodesInPath nodesInPath = INodesInPath.resolve(fsdir.rootDir,
components, false);
// The number of inodes should be equal to components.length
assertEquals(nodesInPath.length(), components.length);
// The returned nodesInPath should be non-snapshot
assertSnapshot(nodesInPath, false, null, -1);
// verify components are correct
for (int i=0; i < components.length; i++) {
assertEquals(components[i], nodesInPath.getPathComponent(i));
}
// The last INode should be associated with file1
assertTrue("file1=" + file1 + ", nodesInPath=" + nodesInPath,
nodesInPath.getINode(components.length - 1) != null);
assertEquals(nodesInPath.getINode(components.length - 1).getFullPathName(),
file1.toString());
assertEquals(nodesInPath.getINode(components.length - 2).getFullPathName(),
sub1.toString());
assertEquals(nodesInPath.getINode(components.length - 3).getFullPathName(),
dir.toString());
assertEquals(Path.SEPARATOR, nodesInPath.getPath(0));
assertEquals(dir.toString(), nodesInPath.getPath(1));
assertEquals(sub1.toString(), nodesInPath.getPath(2));
assertEquals(file1.toString(), nodesInPath.getPath(3));
assertEquals(file1.getParent().toString(),
nodesInPath.getParentINodesInPath().getPath());
nodesInPath = INodesInPath.resolve(fsdir.rootDir, components, false);
assertEquals(nodesInPath.length(), components.length);
assertSnapshot(nodesInPath, false, null, -1);
assertEquals(nodesInPath.getLastINode().getFullPathName(), file1.toString());
}
/**
* for snapshot file.
*/
@Test (timeout=15000)
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);
hdfs.createSnapshot(sub1, "s1");
// The path when accessing the snapshot file of file1 is
// /TestSnapshot/sub1/.snapshot/s1/file1
String snapshotPath = sub1.toString() + "/.snapshot/s1/file1";
byte[][] components = INode.getPathComponents(snapshotPath);
INodesInPath nodesInPath = INodesInPath.resolve(fsdir.rootDir,
components, false);
// Length of inodes should be (components.length - 1), since we will ignore
// ".snapshot"
assertEquals(nodesInPath.length(), components.length - 1);
// SnapshotRootIndex should be 3: {root, Testsnapshot, sub1, s1, file1}
final Snapshot snapshot = getSnapshot(nodesInPath, "s1", 3);
assertSnapshot(nodesInPath, true, snapshot, 3);
assertEquals(".snapshot/s1",
DFSUtil.bytes2String(nodesInPath.getPathComponent(3)));
assertTrue(nodesInPath.getINode(3) instanceof Snapshot.Root);
assertEquals("s1", nodesInPath.getINode(3).getLocalName());
// Check the INode for file1 (snapshot file)
INode snapshotFileNode = nodesInPath.getLastINode();
assertINodeFile(snapshotFileNode, file1);
assertTrue(snapshotFileNode.getParent().isWithSnapshot());
// Call getExistingPathINodes and request only one INode.
nodesInPath = INodesInPath.resolve(fsdir.rootDir, components, false);
assertEquals(nodesInPath.length(), components.length - 1);
assertSnapshot(nodesInPath, true, snapshot, 3);
// Check the INode for file1 (snapshot file)
assertINodeFile(nodesInPath.getLastINode(), file1);
// Resolve the path "/TestSnapshot/sub1/.snapshot"
String dotSnapshotPath = sub1.toString() + "/.snapshot";
components = INode.getPathComponents(dotSnapshotPath);
nodesInPath = INodesInPath.resolve(fsdir.rootDir, components, false);
// The number of INodes returned should still be components.length
// since we put a null in the inode array for ".snapshot"
assertEquals(nodesInPath.length(), components.length);
assertEquals(".snapshot",
DFSUtil.bytes2String(nodesInPath.getLastLocalName()));
assertNull(nodesInPath.getLastINode());
// ensure parent inodes can strip the .snapshot
assertEquals(sub1.toString(),
nodesInPath.getParentINodesInPath().getPath());
// No SnapshotRoot dir is included in the resolved inodes
assertSnapshot(nodesInPath, true, snapshot, -1);
// The last INode should be null, the last but 1 should be sub1
assertNull(nodesInPath.getLastINode());
assertEquals(nodesInPath.getINode(-2).getFullPathName(), sub1.toString());
assertTrue(nodesInPath.getINode(-2).isDirectory());
String[] invalidPathComponent = {"invalidDir", "foo", ".snapshot", "bar"};
Path invalidPath = new Path(invalidPathComponent[0]);
for(int i = 1; i < invalidPathComponent.length; i++) {
invalidPath = new Path(invalidPath, invalidPathComponent[i]);
try {
hdfs.getFileStatus(invalidPath);
Assert.fail();
} catch(FileNotFoundException fnfe) {
System.out.println("The exception is expected: " + fnfe);
}
}
hdfs.deleteSnapshot(sub1, "s1");
hdfs.disallowSnapshot(sub1);
}
/**
* for snapshot file after deleting the original file.
*/
@Test (timeout=15000)
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);
hdfs.createSnapshot(sub1, "s2");
// Delete the original file /TestSnapshot/sub1/file1
hdfs.delete(file1, false);
final Snapshot snapshot;
{
// Resolve the path for the snapshot file
// /TestSnapshot/sub1/.snapshot/s2/file1
String snapshotPath = sub1.toString() + "/.snapshot/s2/file1";
byte[][] components = INode.getPathComponents(snapshotPath);
INodesInPath nodesInPath = INodesInPath.resolve(fsdir.rootDir,
components, false);
// Length of inodes should be (components.length - 1), since we will ignore
// ".snapshot"
assertEquals(nodesInPath.length(), components.length - 1);
// SnapshotRootIndex should be 3: {root, Testsnapshot, sub1, s2, file1}
snapshot = getSnapshot(nodesInPath, "s2", 3);
assertSnapshot(nodesInPath, true, snapshot, 3);
// Check the INode for file1 (snapshot file)
final INode inode = nodesInPath.getLastINode();
assertEquals(file1.getName(), inode.getLocalName());
assertTrue(inode.asFile().isWithSnapshot());
}
// Check the INodes for path /TestSnapshot/sub1/file1
byte[][] components = INode.getPathComponents(file1.toString());
INodesInPath nodesInPath = INodesInPath.resolve(fsdir.rootDir,
components, false);
// The length of inodes should be equal to components.length
assertEquals(nodesInPath.length(), components.length);
// The number of non-null elements should be components.length - 1 since
// file1 has been deleted
assertEquals(getNumNonNull(nodesInPath), components.length - 1);
// The returned nodesInPath should be non-snapshot
assertSnapshot(nodesInPath, false, snapshot, -1);
// The last INode should be null, and the one before should be associated
// with sub1
assertNull(nodesInPath.getINode(components.length - 1));
assertEquals(nodesInPath.getINode(components.length - 2).getFullPathName(),
sub1.toString());
assertEquals(nodesInPath.getINode(components.length - 3).getFullPathName(),
dir.toString());
hdfs.deleteSnapshot(sub1, "s2");
hdfs.disallowSnapshot(sub1);
}
private int getNumNonNull(INodesInPath iip) {
for (int i = iip.length() - 1; i >= 0; i--) {
if (iip.getINode(i) != null) {
return i+1;
}
}
return 0;
}
/**
* for snapshot file while adding a new file after snapshot.
*/
@Test (timeout=15000)
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);
hdfs.createSnapshot(sub1, "s4");
// Add a new file /TestSnapshot/sub1/file3
final Path file3 = new Path(sub1, "file3");
DFSTestUtil.createFile(hdfs, file3, 1024, REPLICATION, seed);
Snapshot s4;
{
// Check the inodes for /TestSnapshot/sub1/.snapshot/s4/file3
String snapshotPath = sub1.toString() + "/.snapshot/s4/file3";
byte[][] components = INode.getPathComponents(snapshotPath);
INodesInPath nodesInPath = INodesInPath.resolve(fsdir.rootDir,
components, false);
// Length of inodes should be (components.length - 1), since we will ignore
// ".snapshot"
assertEquals(nodesInPath.length(), components.length - 1);
// The number of non-null inodes should be components.length - 2, since
// snapshot of file3 does not exist
assertEquals(getNumNonNull(nodesInPath), components.length - 2);
s4 = getSnapshot(nodesInPath, "s4", 3);
// SnapshotRootIndex should still be 3: {root, Testsnapshot, sub1, s4, null}
assertSnapshot(nodesInPath, true, s4, 3);
// Check the last INode in inodes, which should be null
assertNull(nodesInPath.getINode(nodesInPath.length() - 1));
}
// Check the inodes for /TestSnapshot/sub1/file3
byte[][] components = INode.getPathComponents(file3.toString());
INodesInPath nodesInPath = INodesInPath.resolve(fsdir.rootDir,
components, false);
// The number of inodes should be equal to components.length
assertEquals(nodesInPath.length(), components.length);
// The returned nodesInPath should be non-snapshot
assertSnapshot(nodesInPath, false, s4, -1);
// The last INode should be associated with file3
assertEquals(nodesInPath.getINode(components.length - 1).getFullPathName(),
file3.toString());
assertEquals(nodesInPath.getINode(components.length - 2).getFullPathName(),
sub1.toString());
assertEquals(nodesInPath.getINode(components.length - 3).getFullPathName(),
dir.toString());
hdfs.deleteSnapshot(sub1, "s4");
hdfs.disallowSnapshot(sub1);
}
/**
* for snapshot file while modifying file after snapshot.
*/
@Test (timeout=15000)
public void testSnapshotPathINodesAfterModification() throws Exception {
// First check the INode for /TestSnapshot/sub1/file1
byte[][] components = INode.getPathComponents(file1.toString());
INodesInPath nodesInPath = INodesInPath.resolve(fsdir.rootDir,
components, false);
// The number of inodes should be equal to components.length
assertEquals(nodesInPath.length(), components.length);
// The last INode should be associated with file1
assertEquals(nodesInPath.getINode(components.length - 1).getFullPathName(),
file1.toString());
// record the modification time of the inode
final long modTime = nodesInPath.getINode(nodesInPath.length() - 1)
.getModificationTime();
// Create a snapshot for the dir, and check the inodes for the path
// pointing to a snapshot file
hdfs.allowSnapshot(sub1);
hdfs.createSnapshot(sub1, "s3");
// Modify file1
DFSTestUtil.appendFile(hdfs, file1, "the content for appending");
// Check the INodes for snapshot of file1
String snapshotPath = sub1.toString() + "/.snapshot/s3/file1";
components = INode.getPathComponents(snapshotPath);
INodesInPath ssNodesInPath = INodesInPath.resolve(fsdir.rootDir,
components, false);
// Length of ssInodes should be (components.length - 1), since we will
// ignore ".snapshot"
assertEquals(ssNodesInPath.length(), components.length - 1);
final Snapshot s3 = getSnapshot(ssNodesInPath, "s3", 3);
assertSnapshot(ssNodesInPath, true, s3, 3);
// Check the INode for snapshot of file1
INode snapshotFileNode = ssNodesInPath.getLastINode();
assertEquals(snapshotFileNode.getLocalName(), file1.getName());
assertTrue(snapshotFileNode.asFile().isWithSnapshot());
// The modification time of the snapshot INode should be the same with the
// original INode before modification
assertEquals(modTime,
snapshotFileNode.getModificationTime(ssNodesInPath.getPathSnapshotId()));
// Check the INode for /TestSnapshot/sub1/file1 again
components = INode.getPathComponents(file1.toString());
INodesInPath newNodesInPath = INodesInPath.resolve(fsdir.rootDir,
components, false);
assertSnapshot(newNodesInPath, false, s3, -1);
// The number of inodes should be equal to components.length
assertEquals(newNodesInPath.length(), components.length);
// The last INode should be associated with file1
final int last = components.length - 1;
assertEquals(newNodesInPath.getINode(last).getFullPathName(),
file1.toString());
// The modification time of the INode for file3 should have been changed
Assert.assertFalse(modTime == newNodesInPath.getINode(last).getModificationTime());
hdfs.deleteSnapshot(sub1, "s3");
hdfs.disallowSnapshot(sub1);
}
@Test
public void testShortCircuitSnapshotSearch() throws SnapshotException {
FSNamesystem fsn = cluster.getNamesystem();
SnapshotManager sm = fsn.getSnapshotManager();
assertEquals(0, sm.getNumSnapshottableDirs());
INodesInPath iip = Mockito.mock(INodesInPath.class);
List<INodeDirectory> snapDirs = new ArrayList<>();
FSDirSnapshotOp.checkSnapshot(fsn.getFSDirectory(), iip, snapDirs);
Mockito.verifyZeroInteractions(iip);
}
}