/**
* 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;
import java.io.DataOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Random;
import junit.framework.TestCase;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.impl.Log4JLogger;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.BlockLocation;
import org.apache.hadoop.fs.BlockMissingException;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.ChecksumException;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hdfs.DFSUtil;
import org.apache.hadoop.hdfs.DistributedFileSystem;
import org.apache.hadoop.hdfs.protocol.Block;
import org.apache.hadoop.hdfs.protocol.LocatedBlock;
import org.apache.hadoop.hdfs.protocol.LocatedBlocks;
import org.apache.hadoop.hdfs.protocol.ClientProtocol;
import org.apache.hadoop.hdfs.protocol.DatanodeInfo;
import org.apache.hadoop.hdfs.server.common.GenerationStamp;
import org.apache.hadoop.hdfs.server.datanode.BlockInlineChecksumReader;
import org.apache.hadoop.hdfs.server.datanode.DataNode;
import org.apache.hadoop.hdfs.server.protocol.DatanodeRegistration;
import org.apache.hadoop.hdfs.server.namenode.FSNamesystem;
import org.apache.hadoop.hdfs.server.namenode.NameNode;
import org.apache.log4j.Level;
/**
* A JUnit test for corrupted file handling.
*/
public class TestFileCorruption extends TestCase {
{
((Log4JLogger)NameNode.stateChangeLog).getLogger().setLevel(Level.ALL);
((Log4JLogger)FSNamesystem.LOG).getLogger().setLevel(Level.ALL);
((Log4JLogger)DFSClient.LOG).getLogger().setLevel(Level.ALL);
DataNode.LOG.getLogger().setLevel(Level.ALL);
}
static Log LOG = ((Log4JLogger)NameNode.stateChangeLog);
private static String TEST_ROOT_DIR = System.getProperty("test.build.data", "build/test/data");
public TestFileCorruption(String testName) {
super(testName);
}
protected void setUp() throws Exception {
}
protected void tearDown() throws Exception {
}
/** check if DFS can handle corrupted blocks properly */
public void testFileCorruption() throws Exception {
MiniDFSCluster cluster = null;
DFSTestUtil util = new DFSTestUtil("TestFileCorruption", 20, 3, 8*1024);
try {
Configuration conf = new Configuration();
cluster = new MiniDFSCluster(conf, 3, true, null);
FileSystem fs = cluster.getFileSystem();
util.createFiles(fs, "/srcdat");
// Now deliberately remove the blocks
File data_dir = cluster.getBlockDirectory("data5");
assertTrue("data directory does not exist", data_dir.exists());
File[] blocks = data_dir.listFiles();
assertTrue("Blocks do not exist in data-dir", (blocks != null) && (blocks.length > 0));
for (int idx = 0; idx < blocks.length; idx++) {
if (!blocks[idx].getName().startsWith("blk_")) {
continue;
}
System.out.println("Deliberately removing file "+blocks[idx].getName());
assertTrue("Cannot remove file.", blocks[idx].delete());
}
assertTrue("Corrupted replicas not handled properly.",
util.checkFiles(fs, "/srcdat"));
util.cleanup(fs, "/srcdat");
} finally {
if (cluster != null) { cluster.shutdown(); }
}
}
/** check if local FS can handle corrupted blocks properly */
public void testLocalFileCorruption() throws Exception {
Configuration conf = new Configuration();
testFileCorruptionHelper(conf);
}
/** check if local FS can handle corrupted blocks properly in short-circuit mode */
public void testShortCircuitLocalFileWithCorruption() throws Exception {
Configuration conf = new Configuration();
conf.setBoolean("dfs.read.shortcircuit", true);
testFileCorruptionHelper(conf);
}
private void testFileCorruptionHelper(Configuration conf) throws Exception {
Path file = new Path(TEST_ROOT_DIR, "corruptFile");
FileSystem fs = FileSystem.getLocal(conf);
DataOutputStream dos = fs.create(file);
dos.writeBytes("original bytes");
dos.close();
// Now deliberately corrupt the file
dos = new DataOutputStream(new FileOutputStream(file.toString()));
dos.writeBytes("corruption");
dos.close();
// Now attempt to read the file
DataInputStream dis = fs.open(file, 512);
try {
System.out.println("A ChecksumException is expected to be logged.");
dis.readByte();
} catch (ChecksumException ignore) {
//expect this exception but let any NPE get thrown
}
fs.delete(file, true);
}
/** Test the case that a replica is reported corrupt while it is not
* in blocksMap. Make sure that ArrayIndexOutOfBounds does not thrown.
* See Hadoop-4351.
*/
public void testArrayOutOfBoundsException() throws Exception {
MiniDFSCluster cluster = null;
try {
Configuration conf = new Configuration();
cluster = new MiniDFSCluster(conf, 2, true, null);
cluster.waitActive();
int namespaceId = cluster.getNameNode().getNamespaceID();
FileSystem fs = cluster.getFileSystem();
final Path FILE_PATH = new Path("/tmp.txt");
final long FILE_LEN = 1L;
DFSTestUtil.createFile(fs, FILE_PATH, FILE_LEN, (short)2, 1L);
// get the block
File dataDir = cluster.getBlockDirectory("data1");
Block blk = getBlock(dataDir);
if (blk == null) {
blk = getBlock(cluster.getBlockDirectory("data2"));
}
assertFalse(blk==null);
// start a third datanode
cluster.startDataNodes(conf, 1, true, null, null);
ArrayList<DataNode> datanodes = cluster.getDataNodes();
assertEquals(datanodes.size(), 3);
DataNode dataNode = datanodes.get(2);
DatanodeRegistration dnRegistration = dataNode.getDNRegistrationForNS(namespaceId);
// report corrupted block by the third datanode
cluster.getNameNode().namesystem.markBlockAsCorrupt(blk,
new DatanodeInfo(dnRegistration ));
// open the file
fs.open(FILE_PATH);
//clean up
fs.delete(FILE_PATH, false);
} finally {
if (cluster != null) { cluster.shutdown(); }
}
}
private Block getBlock(File dataDir) {
assertTrue("data directory does not exist", dataDir.exists());
File[] blocks = dataDir.listFiles();
assertTrue("Blocks do not exist in dataDir", (blocks != null) && (blocks.length > 0));
int idx = 0;
String blockFileName = null;
boolean isInlineChecksum = false;
for (; idx < blocks.length; idx++) {
blockFileName = blocks[idx].getName();
if (Block.isInlineChecksumBlockFilename(blockFileName)) {
isInlineChecksum = true;
break;
} else if (blockFileName.startsWith("blk_") && !blockFileName.endsWith(".meta")) {
break;
}
}
if (blockFileName == null) {
return null;
}
long blockId = Long.parseLong(blockFileName.substring("blk_".length())
.split("_")[0]);
long blockTimeStamp = GenerationStamp.WILDCARD_STAMP;
if (!isInlineChecksum) {
for (int idx1 = 0; idx1 < blocks.length; idx1++) {
String fileName = blocks[idx].getName();
if (fileName.startsWith(blockFileName) && fileName.endsWith(".meta")) {
int startIndex = blockFileName.length() + 1;
int endIndex = fileName.length() - ".meta".length();
blockTimeStamp = Long.parseLong(fileName.substring(startIndex,
endIndex));
break;
}
}
} else {
blockTimeStamp = Long.parseLong(blockFileName.split("_")[2]);
}
return new Block(blockId, blocks[idx].length(), blockTimeStamp);
}
/** check if ClientProtocol.getCorruptFiles() returns a file that has missing blocks */
@SuppressWarnings("deprecation")
public void testCorruptFilesMissingBlock() throws Exception {
MiniDFSCluster cluster = null;
try {
Configuration conf = new Configuration();
conf.setInt("dfs.datanode.directoryscan.interval", 1); // datanode scans directories
conf.setInt("dfs.blockreport.intervalMsec", 3 * 1000); // datanode sends block reports
cluster = new MiniDFSCluster(conf, 1, true, null);
FileSystem fs = cluster.getFileSystem();
// create two files with one block each
DFSTestUtil util = new DFSTestUtil("testCorruptFilesMissingBlock", 2, 1, 512);
util.createFiles(fs, "/srcdat");
// verify that there are no bad blocks.
ClientProtocol namenode = DFSClient.createNamenode(conf);
FileStatus[] badFiles = namenode.getCorruptFiles();
assertTrue("Namenode has " + badFiles.length + " corrupt files. Expecting none.",
badFiles.length == 0);
// Now deliberately remove one block
File data_dir = cluster.getBlockDirectory("data1");
assertTrue("data directory does not exist", data_dir.exists());
File[] blocks = data_dir.listFiles();
assertTrue("Blocks do not exist in data-dir", (blocks != null) && (blocks.length > 0));
for (int idx = 0; idx < blocks.length; idx++) {
if (!blocks[idx].getName().startsWith("blk_")) {
continue;
}
LOG.info("Deliberately removing file "+blocks[idx].getName());
assertTrue("Cannot remove file.", blocks[idx].delete());
break;
}
badFiles = namenode.getCorruptFiles();
while (badFiles.length == 0) {
Thread.sleep(1000);
badFiles = namenode.getCorruptFiles();
}
LOG.info("Namenode has bad files. " + badFiles.length);
assertTrue("Namenode has " + badFiles.length + " bad files. Expecting 1.",
badFiles.length == 1);
util.cleanup(fs, "/srcdat");
} finally {
if (cluster != null) { cluster.shutdown(); }
}
}
/**
* check if listCorruptFileBlocks() returns the right number of
* corrupt files if there are two corrupt files with the same name
* in different directories
*/
public void test2CorruptFilesWithSameName() throws Exception {
MiniDFSCluster cluster = null;
Random random = new Random();
try {
Configuration conf = new Configuration();
// datanode scans directories
conf.setInt("dfs.datanode.directoryscan.interval", 1);
// datanode sends block reports
conf.setInt("dfs.blockreport.intervalMsec", 3 * 1000);
conf.setBoolean("dfs.permissions", false);
cluster = new MiniDFSCluster(conf, 1, true, null);
FileSystem fs = cluster.getFileSystem();
assertTrue("fs is not a DFS", fs instanceof DistributedFileSystem);
DistributedFileSystem dfs = (DistributedFileSystem) fs;
Path file1 = new Path("/srcdat12/test2file.test");
Path file2 = new Path("/srcdat13/test2file.test");
// create two files with the same name
DFSTestUtil.createFile(fs, file1, 1L, (short)1, 1L);
DFSTestUtil.createFile(fs, file2, 1L, (short)1, 1L);
// fetch bad file list from namenode. There should be none.
ClientProtocol namenode = DFSClient.createNamenode(conf);
String[] badFiles = DFSUtil.getCorruptFiles(dfs);
assertTrue("Namenode has " + badFiles.length +
" corrupt files. Expecting None.",
badFiles.length == 0);
// Now deliberately corrupt one block in each file
Path[] files = {file1, file2};
for (Path file: files) {
LocatedBlocks fileBlocks =
namenode.getBlockLocations(file.toString(), 0, 1L);
LocatedBlock block = fileBlocks.get(0);
File data_dir =
new File(TEST_ROOT_DIR, "dfs/data/");
File dir1 = cluster.getBlockDirectory("data"+(2 * 0 + 1));
File dir2 = cluster.getBlockDirectory("data"+(2 * 0 + 2));
if (!(dir1.isDirectory() && dir2.isDirectory())) {
throw new IOException("data directories not found for data node 0: " +
dir1.toString() + " " + dir2.toString());
}
File[] dirs = new File[2];
dirs[0] = dir1;
dirs[1] = dir2;
for (File dir: dirs) {
File[] blockFiles = dir.listFiles();
for (File blockFile: blockFiles) {
if ((blockFile.getName().
startsWith("blk_" + block.getBlock().getBlockId())) &&
(!blockFile.getName().endsWith(".meta"))) {
blockFile.delete();
}
}
}
LocatedBlock[] toReport = { block };
namenode.reportBadBlocks(toReport);
}
// fetch bad file list from namenode. There should be 2.
badFiles = DFSUtil.getCorruptFiles(dfs);
assertTrue("Namenode has " + badFiles.length + " bad files. Expecting 2.",
badFiles.length == 2);
} finally {
if (cluster != null) {
cluster.shutdown();
}
}
}
}