package org.apache.hadoop.hdfs; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.Random; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FSDataInputStream; import org.apache.hadoop.fs.FSDataOutputStream; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.hdfs.DFSOutputStream; import org.apache.hadoop.hdfs.DFSTestUtil; import org.apache.hadoop.hdfs.MiniDFSCluster; import org.apache.hadoop.hdfs.protocol.FSConstants; import org.apache.hadoop.hdfs.server.common.HdfsConstants.StartupOption; import org.apache.hadoop.hdfs.server.datanode.DataNode; import org.apache.hadoop.hdfs.server.namenode.NameNode; import org.apache.hadoop.hdfs.server.protocol.NamespaceInfo; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; /** * This tests datanode upgrades and rollbacks and verifies that no data is lost * in these operations. */ public class TestDatanodeUpgrade { private static Configuration conf; private static MiniDFSCluster cluster; private static long oldCTime = 0L; private static final int lastBlockLen = 30; private static final int BLOCK_SIZE = 512; // Needed to run unit tests sequentially. private final static Lock lock = new ReentrantLock(); @Before public void setUp() throws Exception { lock.lock(); Field layoutVersion = FSConstants.class.getField("LAYOUT_VERSION"); conf = new Configuration(); conf.setInt("dfs.block.size", BLOCK_SIZE); // Conf options to speed up unit test execution. conf.setInt("dfs.client.block.recovery.retries", 1); conf.setInt("ipc.client.connect.max.retries", 1); conf.setInt("dfs.socket.timeout", 500); cluster = new MiniDFSCluster(conf, 3, true, null); } @After public void tearDown() throws Exception { cluster.finalizeCluster(conf); cluster.shutdown(); NameNode.format(conf); cluster.formatDataNodeDirs(); lock.unlock(); } @Test public void testUpgradeNoRBW() throws Exception { DFSTestUtil util = new DFSTestUtil("testUpgradeNoRBW", 10, 3, 6 * BLOCK_SIZE + lastBlockLen); util.createFiles(cluster.getFileSystem(), "/"); NamespaceInfo nsInfo = cluster.getNameNode().versionRequest(); for (DataNode dn : cluster.getDataNodes()) { assertEquals(oldCTime, dn.getCTime(nsInfo.getNamespaceID())); } cluster.shutdown(); cluster = new MiniDFSCluster(0, conf, 3, false, true, StartupOption.UPGRADE, null); nsInfo = cluster.getNameNode().versionRequest(); for (DataNode dn : cluster.getDataNodes()) { assertEquals(nsInfo.getCTime(), dn.getCTime(nsInfo.getNamespaceID())); } assertTrue(util.checkFiles(cluster.getFileSystem(), "/")); } // Create a file with RBWs private void createFile(FileSystem fs, FSDataOutputStream out, String fileName, int fileLen) throws IOException { Random random = new Random(fileName.hashCode()); byte buffer[] = new byte[fileLen]; random.nextBytes(buffer); out.write(buffer); out.sync(); ((DFSOutputStream) out.getWrappedStream()).abortForTests(); } private void verifyFile(FileSystem fs, String fileName, int fileLen) throws IOException { FSDataInputStream in = fs.open(new Path(fileName)); byte expected[] = new byte[fileLen]; byte actual[] = new byte[fileLen]; Random random = new Random(fileName.hashCode()); random.nextBytes(expected); in.readFully(actual); assertTrue(Arrays.equals(expected, actual)); } @Test public void testUpgradeRBW() throws Exception { String fileName = "/testUpgradeRBW"; int fileLen = 6 * BLOCK_SIZE + lastBlockLen; FileSystem fs = cluster.getFileSystem(); FSDataOutputStream out = fs.create(new Path(fileName)); createFile(fs, out, fileName, fileLen); // After the fsync, the last block len is set to 1. assertEquals(fileLen - (lastBlockLen - 1), fs.getFileStatus(new Path(fileName)).getLen()); NamespaceInfo nsInfo = cluster.getNameNode().versionRequest(); for (DataNode dn : cluster.getDataNodes()) { assertEquals(1, dn.data.getBlocksBeingWrittenReport(nsInfo.getNamespaceID()).length); } // Restart namenode and datanodes and perform an upgrade. cluster.shutdown(); cluster = new MiniDFSCluster(0, conf, 3, false, true, StartupOption.UPGRADE, null); fs = cluster.getFileSystem(); nsInfo = cluster.getNameNode().versionRequest(); for (DataNode dn : cluster.getDataNodes()) { assertEquals(nsInfo.getCTime(), dn.getCTime(nsInfo.getNamespaceID())); } // After the fsync, the last block len is set to 1. assertEquals(fileLen - (lastBlockLen - 1), fs.getFileStatus(new Path(fileName)).getLen()); for (DataNode dn : cluster.getDataNodes()) { assertEquals(1, dn.data.getBlocksBeingWrittenReport(nsInfo.getNamespaceID()).length); } // Verify sanity of files. verifyFile(fs, fileName, fileLen); // Now lets do a rollback and verify we still have the data. cluster.shutdown(); cluster = new MiniDFSCluster(0, conf, 3, false, true, StartupOption.ROLLBACK, null); fs = cluster.getFileSystem(); nsInfo = cluster.getNameNode().versionRequest(); for (DataNode dn : cluster.getDataNodes()) { assertEquals(oldCTime, dn.getCTime(nsInfo.getNamespaceID())); } // After the fsync, the last block len is set to 1. assertEquals(fileLen - (lastBlockLen - 1), fs.getFileStatus(new Path(fileName)).getLen()); for (DataNode dn : cluster.getDataNodes()) { assertEquals(1, dn.data.getBlocksBeingWrittenReport(nsInfo.getNamespaceID()).length); } // Verify sanity of files. verifyFile(fs, fileName, fileLen); } }