HDFS-2773. Reading edit logs from an earlier version should not leave blocks in under-construction state. Contributed by Todd Lipcon.

git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/branches/HDFS-1623@1229900 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Todd Lipcon 2012-01-11 06:08:13 +00:00
parent 190dc1c91b
commit 298e867673
5 changed files with 108 additions and 2 deletions

View File

@ -95,3 +95,5 @@ HDFS-2762. Fix TestCheckpoint timing out on HA branch. (Uma Maheswara Rao G via
HDFS-2724. NN web UI can throw NPE after startup, before standby state is entered. (todd)
HDFS-2753. Fix standby getting stuck in safemode when blocks are written while SBN is down. (Hari Mankude and todd via todd)
HDFS-2773. Reading edit logs from an earlier version should not leave blocks in under-construction state. (todd)

View File

@ -467,7 +467,7 @@ public class FSEditLogLoader {
BlockInfo oldBlock = oldBlocks[i];
Block newBlock = addCloseOp.blocks[i];
boolean isLastBlock = i == oldBlocks.length - 1;
boolean isLastBlock = i == addCloseOp.blocks.length - 1;
if (oldBlock.getBlockId() != newBlock.getBlockId() ||
(oldBlock.getGenerationStamp() != newBlock.getGenerationStamp() &&
!(isGenStampUpdate && isLastBlock))) {
@ -504,7 +504,17 @@ public class FSEditLogLoader {
// We're adding blocks
for (int i = oldBlocks.length; i < addCloseOp.blocks.length; i++) {
Block newBlock = addCloseOp.blocks[i];
BlockInfo newBI = new BlockInfoUnderConstruction(newBlock, file.getReplication());
BlockInfo newBI;
if (addCloseOp.opCode == FSEditLogOpCodes.OP_ADD){
newBI = new BlockInfoUnderConstruction(
newBlock, file.getReplication());
} else {
// OP_CLOSE should add finalized blocks. This code path
// is only executed when loading edits written by prior
// versions of Hadoop. Current versions always log
// OP_ADD operations as each block is allocated.
newBI = new BlockInfo(newBlock, file.getReplication());
}
fsNamesys.getBlockManager().addINode(newBI, file);
file.addBlock(newBI);
}

View File

@ -26,6 +26,8 @@ import org.apache.hadoop.hdfs.server.blockmanagement.BlockInfoUnderConstruction;
import org.apache.hadoop.hdfs.server.blockmanagement.DatanodeDescriptor;
import org.apache.hadoop.hdfs.server.common.HdfsServerConstants.BlockUCState;
import com.google.common.base.Joiner;
/**
* I-node for file being written.
*/
@ -94,6 +96,9 @@ public class INodeFileUnderConstruction extends INodeFile {
// use the modification time as the access time
//
INodeFile convertToInodeFile() {
assert allBlocksComplete() :
"Can't finalize inode " + this + " since it contains " +
"non-complete blocks! Blocks are: " + blocksAsString();
INodeFile obj = new INodeFile(getPermissionStatus(),
getBlocks(),
getReplication(),
@ -103,6 +108,18 @@ public class INodeFileUnderConstruction extends INodeFile {
return obj;
}
/**
* @return true if all of the blocks in this file are marked as completed.
*/
private boolean allBlocksComplete() {
for (BlockInfo b : blocks) {
if (!b.isComplete()) {
return false;
}
}
return true;
}
/**
* Remove a block from the block list. This block should be
@ -141,4 +158,8 @@ public class INodeFileUnderConstruction extends INodeFile {
setBlock(numBlocks()-1, ucBlock);
return ucBlock;
}
private String blocksAsString() {
return Joiner.on(",").join(this.blocks);
}
}

View File

@ -23,22 +23,34 @@ import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.CommonConfigurationKeysPublic;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.FileUtil;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.hdfs.protocol.LocatedBlock;
import org.apache.hadoop.hdfs.protocol.LocatedBlocks;
import org.apache.hadoop.hdfs.server.common.HdfsServerConstants.StartupOption;
import org.apache.hadoop.hdfs.server.namenode.FSEditLog;
import org.apache.hadoop.hdfs.server.namenode.FSImage;
import org.apache.hadoop.hdfs.server.namenode.FSImageTestUtil;
import org.apache.hadoop.hdfs.server.namenode.FSNamesystem;
import org.apache.hadoop.hdfs.server.namenode.INodeFileUnderConstruction;
import org.apache.hadoop.hdfs.server.namenode.NameNode;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.test.GenericTestUtils;
import org.apache.log4j.Level;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.Collection;
import java.util.List;
import java.util.Random;
import static org.junit.Assert.*;
import org.junit.Test;
import com.google.common.collect.Lists;
/**
* A JUnit test for checking if restarting DFS preserves the
* blocks that are part of an unclosed file.
@ -57,6 +69,9 @@ public class TestPersistBlocks {
static final byte[] DATA_BEFORE_RESTART = new byte[BLOCK_SIZE * NUM_BLOCKS];
static final byte[] DATA_AFTER_RESTART = new byte[BLOCK_SIZE * NUM_BLOCKS];
private static final String HADOOP_1_0_MULTIBLOCK_TGZ =
"hadoop-1.0-multiblock-file.tgz";
static {
Random rand = new Random();
rand.nextBytes(DATA_BEFORE_RESTART);
@ -277,4 +292,62 @@ public class TestPersistBlocks {
if (cluster != null) { cluster.shutdown(); }
}
}
/**
* Earlier versions of HDFS didn't persist block allocation to the edit log.
* This makes sure that we can still load an edit log when the OP_CLOSE
* is the opcode which adds all of the blocks. This is a regression
* test for HDFS-2773.
* This test uses a tarred pseudo-distributed cluster from Hadoop 1.0
* which has a multi-block file. This is similar to the tests in
* {@link TestDFSUpgradeFromImage} but none of those images include
* a multi-block file.
*/
@Test
public void testEarlierVersionEditLog() throws Exception {
final Configuration conf = new HdfsConfiguration();
String tarFile = System.getProperty("test.cache.data", "build/test/cache")
+ "/" + HADOOP_1_0_MULTIBLOCK_TGZ;
String testDir = System.getProperty("test.build.data", "build/test/data");
File dfsDir = new File(testDir, "image-1.0");
if (dfsDir.exists() && !FileUtil.fullyDelete(dfsDir)) {
throw new IOException("Could not delete dfs directory '" + dfsDir + "'");
}
FileUtil.unTar(new File(tarFile), new File(testDir));
File nameDir = new File(dfsDir, "name");
GenericTestUtils.assertExists(nameDir);
File dataDir = new File(dfsDir, "data");
GenericTestUtils.assertExists(dataDir);
conf.set(DFSConfigKeys.DFS_NAMENODE_NAME_DIR_KEY, nameDir.getAbsolutePath());
conf.set(DFSConfigKeys.DFS_DATANODE_DATA_DIR_KEY, dataDir.getAbsolutePath());
MiniDFSCluster cluster = new MiniDFSCluster.Builder(conf).numDataNodes(0)
.format(false)
.manageDataDfsDirs(false)
.manageNameDfsDirs(false)
.numDataNodes(1)
.startupOption(StartupOption.UPGRADE)
.build();
try {
FileSystem fs = cluster.getFileSystem();
Path testPath = new Path("/user/todd/4blocks");
// Read it without caring about the actual data within - we just need
// to make sure that the block states and locations are OK.
DFSTestUtil.readFile(fs, testPath);
// Ensure that we can append to it - if the blocks were in some funny
// state we'd get some kind of issue here.
FSDataOutputStream stm = fs.append(testPath);
try {
stm.write(1);
} finally {
IOUtils.closeStream(stm);
}
} finally {
cluster.shutdown();
}
}
}