package org.apache.hadoop.raid; import java.io.File; import java.io.IOException; import java.util.Random; import junit.framework.TestCase; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileStatus; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.hdfs.DFSUtil; import org.apache.hadoop.hdfs.DistributedFileSystem; import org.apache.hadoop.hdfs.MiniDFSCluster; import org.apache.hadoop.hdfs.RaidDFSUtil; import org.apache.hadoop.hdfs.TestRaidDfs; import org.apache.hadoop.hdfs.protocol.LocatedBlockWithFileName; import org.apache.hadoop.hdfs.protocol.LocatedBlocks; import org.apache.hadoop.mapred.MiniMRCluster; import org.apache.hadoop.raid.LogUtils.LOGRESULTS; import org.apache.hadoop.raid.LogUtils.LOGTYPES; public class TestDirectoryParityRegenerator extends TestCase { final static Log LOG = LogFactory.getLog( "org.apache.hadoop.raid.TestDirectoryBlockFixerWithStripeStore"); final static String TEST_DIR = new File(System.getProperty("test.build.data", "build/contrib/raid/test/data")).getAbsolutePath(); final static String CHECKSUM_STORE_DIR = new File(TEST_DIR, "ckm_store." + System.currentTimeMillis()).getAbsolutePath(); final static String STRIPE_STORE_DIR = new File(TEST_DIR, "stripe_store." + System.currentTimeMillis()).getAbsolutePath(); final static String CONFIG_FILE = new File(TEST_DIR, "test-raid.xml").getAbsolutePath(); final static long RELOAD_INTERVAL = 1000; final static int NUM_DATANODES = 3; final long blockSize = 8192L; final long[] fileSizes = new long[]{blockSize + blockSize/2, // block 0, 1 3*blockSize, // block 2, 3 blockSize + blockSize/2 + 1}; // block 4, 5, 6, 7 final long[] blockSizes = new long[]{blockSize, 2*blockSize, blockSize/2}; final Integer[] rsCorruptFileIdx1 = new Integer[]{0, 1, 2, 3, 5, 6, 7}; final int[] rsNumCorruptBlocksInFiles1 = new int[] {2, 2, 3}; final Integer[] rsCorruptFileIdx2 = new Integer[]{1, 2, 3, 4, 5, 6}; final int[] rsNumCorruptBlocksInFiles2 = new int[] {1, 2, 3}; Configuration conf; String namenode = null; MiniDFSCluster dfsCluster = null; String hftp = null; MiniMRCluster mr = null; FileSystem fileSys = null; RaidNode cnode = null; String jobTrackerName = null; Random rand = new Random(); static { ParityFilePair.disableCacheUsedInTestOnly(); } private void mySetup(int stripeLength) throws Exception { if (System.getProperty("hadoop.log.dir") == null) { String base = new File(".").getAbsolutePath(); System.setProperty("hadoop.log.dir", new Path(base).toString() + "/logs"); } new File(TEST_DIR).mkdirs(); // Make sure data directory exists conf = new Configuration(); conf.set("raid.config.file", CONFIG_FILE); conf.setBoolean("raid.config.reload", true); conf.setLong("raid.config.reload.interval", RELOAD_INTERVAL); // scan all policies once every 5 second conf.setLong("raid.policy.rescan.interval", 5000); // do not use map-reduce cluster for Raiding conf.set("raid.classname", "org.apache.hadoop.raid.LocalRaidNode"); conf.set("raid.server.address", "localhost:0"); conf.set("mapred.raid.http.address", "localhost:0"); Utils.loadTestCodecs(conf, stripeLength, stripeLength, 1, 3, "/destraid", "/destraidrs", false, true); conf.setBoolean("dfs.permissions", false); dfsCluster = new MiniDFSCluster(conf, NUM_DATANODES, true, null); dfsCluster.waitActive(); fileSys = dfsCluster.getFileSystem(); namenode = fileSys.getUri().toString(); FileSystem.setDefaultUri(conf, namenode); mr = new MiniMRCluster(4, namenode, 3); jobTrackerName = "localhost:" + mr.getJobTrackerPort(); hftp = "hftp://localhost.localdomain:" + dfsCluster.getNameNodePort(); FileSystem.setDefaultUri(conf, namenode); conf.set("mapred.job.tracker", jobTrackerName); TestDirectoryRaidDfs.setupStripeStore(conf, fileSys); ConfigBuilder cb = new ConfigBuilder(CONFIG_FILE); cb.addPolicy("RaidTest1", "/user/dhruba/raidtest", 1, 1); cb.addPolicy("RaidTest2", "/user/dhruba/raidtestrs", 1, 1, "rs"); cb.persist(); } public void testDirParitywithDeletingFiles() throws Exception { implDirParityRegen(0); } public void testDirParitywithAddingFiles() throws Exception { implDirParityRegen(1); } public void testDirParitywithRenamingFiles() throws Exception { implDirParityRegen(2); } private void implDirParityRegen(int operatorId) throws Exception { int stripeLength = 3; mySetup(stripeLength); long[] crcs = new long[3]; int[] seeds = new int[3]; Path dirPath =new Path("/user/dhruba/raidtestrs"); Path[] files = TestRaidDfs.createTestFiles(dirPath, fileSizes, blockSizes, crcs, seeds, fileSys, (short)1); Path destPath = new Path("/destraidrs/user/dhruba"); Configuration localConf = this.getRaidNodeConfig(conf); try { cnode = RaidNode.createRaidNode(null, localConf); TestRaidDfs.waitForDirRaided(LOG, fileSys, dirPath, destPath); cnode.stop(); cnode.join(); DistributedFileSystem dfs = (DistributedFileSystem) fileSys; String[] corruptFiles = DFSUtil.getCorruptFiles(dfs); assertEquals("no corrupt files expected", 0, corruptFiles.length); assertEquals("filesFixed() should return 0 before fixing files", 0, cnode.blockIntegrityMonitor.getNumFilesFixed()); // corrupt files this.corruptFiles(dirPath, crcs, rsCorruptFileIdx1, dfs, files, rsNumCorruptBlocksInFiles1); FileStatus dirStat = dfs.getFileStatus(dirPath); if (operatorId == 0) { // delete file dfs.delete(files[0], true); } else if (operatorId == 1) { // add file TestRaidDfs.createTestFile(dfs, new Path(dirPath, "file3"), (short)1, 5, blockSize); } else { // rename file Path newFile = new Path(files[0].toUri().getPath() + "_rename"); dfs.rename(files[0], newFile); files[0] = newFile; } FileStatus newDirStat = dfs.getFileStatus(dirPath); // assert the modification is changed. assertNotSame(dirStat.getModificationTime(), newDirStat.getModificationTime()); Codec codec = Codec.getCodec("rs"); ParityFilePair pfPair = ParityFilePair.getParityFile(codec, newDirStat, localConf); assertNull(pfPair); cnode = RaidNode.createRaidNode(null, localConf); // wait for the re-generation of the parity files TestRaidDfs.waitForDirRaided(LOG, dfs, dirPath, destPath, (short)1, 240000); TestBlockFixer.verifyMetrics(dfs, cnode, LOGTYPES.MODIFICATION_TIME_CHANGE, LOGRESULTS.NONE, 1L, true); TestBlockFixer.verifyMetrics(dfs, cnode, LOGTYPES.MODIFICATION_TIME_CHANGE, LOGRESULTS.NONE, codec.id, 1L, true); } catch (Exception e) { throw e; } finally { myTearDown(); } } public Configuration getRaidNodeConfig(Configuration conf) { // create an instance of the RaidNode Configuration localConf = new Configuration(conf); // no block fixing here. localConf.set("raid.blockfix.classname", FakeBlockIntegerityMonitor.class.getName()); return localConf; } private void corruptFiles(Path dirPath, long[] crcs, Integer[] corruptBlockIdxs, DistributedFileSystem dfs, Path[] files, int[] numCorruptBlocksInFiles) throws IOException { int totalCorruptFiles = DFSUtil.getCorruptFiles(dfs).length; TestDirectoryRaidDfs.corruptBlocksInDirectory(conf, dirPath, crcs, corruptBlockIdxs, fileSys, dfsCluster, false, true); String[] corruptFiles = DFSUtil.getCorruptFiles(dfs); for (int i = 0; i < numCorruptBlocksInFiles.length; i++) { if (numCorruptBlocksInFiles[i] > 0) totalCorruptFiles++; } assertEquals("files not corrupted", totalCorruptFiles, corruptFiles.length); for (int i = 0; i< fileSizes.length; i++) { assertEquals("wrong number of corrupt blocks for file " + files[i], numCorruptBlocksInFiles[i], RaidDFSUtil.corruptBlocksInFile(dfs, files[i].toUri().getPath(), 0, fileSizes[i]).size()); } } private void myTearDown() throws Exception { if (cnode != null) { cnode.stop(); cnode.join(); } if (mr != null) { mr.shutdown(); } if (dfsCluster != null) { dfsCluster.shutdown(); } } public static class FakeBlockIntegerityMonitor extends BlockIntegrityMonitor implements Runnable { public FakeBlockIntegerityMonitor(Configuration conf) throws Exception { super(conf); } @Override public void run() { // do nothing } @Override public Status getAggregateStatus() { throw new UnsupportedOperationException(FakeBlockIntegerityMonitor.class + " doesn't do getAggregateStatus()"); } @Override public Runnable getCorruptionMonitor() { return this; } @Override public Runnable getDecommissioningMonitor() { return null; } @Override public Runnable getCorruptFileCounter() { return null; } } }