/**
* 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.raid;
import java.io.File;
import java.io.IOException;
import java.util.Random;
import java.util.zip.CRC32;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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.MiniDFSCluster;
import org.apache.hadoop.hdfs.RaidDFSUtil;
import org.apache.hadoop.hdfs.TestRaidDfs;
import junit.framework.TestCase;
public class TestDirectoryRaidEncoder extends TestCase {
final static String TEST_DIR = new File(System.getProperty("test.build.data",
"build/contrib/raid/test/data")).getAbsolutePath();
final static Log LOG = LogFactory.getLog(
"org.apache.hadoop.raid.TestDirectoryRaidEncoder");
final static int NUM_DATANODES = 3;
static {
ParityFilePair.disableCacheUsedInTestOnly();
}
Configuration conf;
String namenode = null;
MiniDFSCluster dfs = null;
FileSystem fileSys = null;
private void mySetup() throws Exception {
new File(TEST_DIR).mkdirs(); // Make sure data directory exists
conf = new Configuration();
conf.setInt("raid.encoder.bufsize", 128);
conf.setInt("raid.decoder.bufsize", 128);
// scan all policies once every 5 second
conf.setLong("raid.policy.rescan.interval", 5000);
// Reduce run time for the test.
conf.setInt("dfs.client.max.block.acquire.failures", 1);
conf.setInt("dfs.client.baseTimeWindow.waitOn.BlockMissingException", 10);
// do not use map-reduce cluster for Raiding
conf.set("raid.classname", "org.apache.hadoop.raid.LocalRaidNode");
conf.set("raid.server.address", "localhost:0");
//Don't allow empty file to be raid
conf.setLong(RaidNode.MINIMUM_RAIDABLE_FILESIZE_KEY, 1L);
dfs = new MiniDFSCluster(conf, NUM_DATANODES, true, null);
dfs.waitActive();
fileSys = dfs.getFileSystem();
namenode = fileSys.getUri().toString();
FileSystem.setDefaultUri(conf, namenode);
}
private Codec loadTestCodecs(String erasureCode, int stripeLength,
boolean isDirRaid) throws Exception {
Utils.loadTestCodecs(conf, stripeLength, stripeLength, 1,
3, "/destraid", "/destraidrs", false, isDirRaid);
return Codec.getCodec(erasureCode);
}
private void myTearDown() throws Exception {
if (dfs != null) { dfs.shutdown(); }
}
private boolean doRaid(Configuration conf, FileSystem fileSys,
Path srcPath, Codec codec)
throws IOException {
return RaidNode.doRaid(conf, fileSys.getFileStatus(srcPath),
new Path(codec.parityDirectory), codec,
new RaidNode.Statistics(),
RaidUtils.NULL_PROGRESSABLE, false, 1, 1);
}
public void testAbnormalDirectory() throws Exception {
mySetup();
Codec codec = loadTestCodecs("xor", 4, true);
try {
Path sourceDir = new Path("/user/raid");
Path parityFile = new Path("/destraid/user/raid");
assertTrue(fileSys.mkdirs(sourceDir));
LOG.info("Test non-leaf directory");
assertFalse("Couldn't raid non-leaf directory ",
doRaid(conf, fileSys, sourceDir.getParent(), codec));
assertFalse(fileSys.exists(parityFile.getParent()));
LOG.info("Test empty directory");
assertFalse("Couldn't raid empty directory ",
doRaid(conf, fileSys, sourceDir, codec));
assertFalse(fileSys.exists(parityFile));
LOG.info("Test empty file in the directory");
Path emptyFile = new Path(sourceDir, "emptyFile");
TestRaidDfs.createTestFile(fileSys, emptyFile, 1, 0, 8192L);
assertTrue(fileSys.exists(emptyFile));
assertFalse("No raidable files in the directory",
doRaid(conf, fileSys, sourceDir, codec));
assertFalse(fileSys.exists(parityFile));
LOG.info("Test not enough blocks in the directory");
Path file1 = new Path(sourceDir, "file1");
Path file2 = new Path(sourceDir, "file2");
TestRaidDfs.createTestFile(fileSys, file1, 1, 1, 8192L);
TestRaidDfs.createTestFile(fileSys, file2, 1, 1, 8192L);
LOG.info("Created two files with two blocks in total");
assertTrue(fileSys.exists(file1));
assertTrue(fileSys.exists(file2));
assertFalse("Not enough blocks in the directory",
doRaid(conf, fileSys, sourceDir, codec));
assertFalse(fileSys.exists(parityFile));
} finally {
myTearDown();
}
}
private void validateSingleFile(String code, FileSystem fileSys,
Path sourceDir, int stripeLength, int blockNum, boolean lastPartial)
throws Exception {
LOG.info("Test file with " + blockNum + " blocks and " +
(lastPartial? "partial": "full") + " last block");
Codec codec = loadTestCodecs(code, stripeLength, true);
Path parityDir = new Path(codec.parityDirectory);
RaidDFSUtil.cleanUp(fileSys, sourceDir);
RaidDFSUtil.cleanUp(fileSys, parityDir);
fileSys.mkdirs(sourceDir);
Path file1 = new Path(sourceDir, "file1");
if (!lastPartial) {
TestRaidDfs.createTestFile(fileSys, file1, 2, blockNum, 8192L);
} else {
TestRaidDfs.createTestFilePartialLastBlock(fileSys, file1, 2,
blockNum, 8192L);
}
Path parityFile = RaidNode.getOriginalParityFile(parityDir, sourceDir);
// Do directory level raid
LOG.info("Create a directory-raid parity file " + parityFile);
assertTrue("Cannot raid directory " + sourceDir,
doRaid(conf, fileSys, sourceDir, codec));
assertEquals("Modification time should be the same",
fileSys.getFileStatus(sourceDir).getModificationTime(),
fileSys.getFileStatus(parityFile).getModificationTime());
assertEquals("Replica num of source file should be reduced to 1",
fileSys.getFileStatus(file1).getReplication(), 1);
assertEquals("Replica num of parity file should be reduced to 1",
fileSys.getFileStatus(parityFile).getReplication(), 1);
long dirCRC = RaidDFSUtil.getCRC(fileSys, parityFile);
long dirLen = fileSys.getFileStatus(parityFile).getLen();
// remove the parity dir
RaidDFSUtil.cleanUp(fileSys, parityDir);
codec = loadTestCodecs(code, stripeLength, false);
Path parityFile1 = RaidNode.getOriginalParityFile(parityDir,
file1);
LOG.info("Create a file-raid parity file " + parityFile1);
assertTrue("Cannot raid file " + file1,
doRaid(conf, fileSys, file1, codec));
assertTrue("Parity file doesn't match when the file has " + blockNum +
" blocks ",
TestRaidDfs.validateFile(fileSys, parityFile1, dirLen, dirCRC));
}
public void testOneFileDirectory() throws Exception {
mySetup();
int stripeLength = 4;
try {
for (String code: RaidDFSUtil.codes) {
LOG.info("testOneFileDirectory: Test code " + code);
Codec codec = loadTestCodecs(code, stripeLength, true);
Path sourceDir = new Path("/user/raid", code);
assertTrue(fileSys.mkdirs(sourceDir));
Path twoBlockFile = new Path(sourceDir, "twoBlockFile");;
LOG.info("Test one file with 2 blocks");
TestRaidDfs.createTestFile(fileSys, twoBlockFile, 2, 2, 8192L);
assertTrue(fileSys.exists(twoBlockFile));
assertFalse("Not enough blocks in the directory",
RaidNode.doRaid(conf, fileSys.getFileStatus(sourceDir),
new Path(codec.parityDirectory), codec,
new RaidNode.Statistics(), RaidUtils.NULL_PROGRESSABLE,
false, 1, 1));
fileSys.delete(twoBlockFile, true);
LOG.info("Test one file with blocks less than one stripe");
validateSingleFile(code, fileSys, sourceDir, stripeLength, 3,
false);
validateSingleFile(code, fileSys, sourceDir, stripeLength, 3,
true);
LOG.info("Test one file with one stripe blocks");
validateSingleFile(code, fileSys, sourceDir, stripeLength,
stripeLength, false);
validateSingleFile(code, fileSys, sourceDir, stripeLength,
stripeLength, true);
LOG.info("Test one file with more than one stripe blocks");
validateSingleFile(code, fileSys, sourceDir, stripeLength,
stripeLength + 2, false);
validateSingleFile(code, fileSys, sourceDir, stripeLength,
stripeLength + 2, true);
}
} finally {
myTearDown();
}
}
private void validateMultipleFiles(String code, FileSystem fileSys,
Path sourceDir, int stripeLength, long[] fileSizes, long blockSize,
long singleFileBlockSize) throws Exception {
long[] blockSizes = new long[fileSizes.length];
for (int i = 0; i< fileSizes.length; i++)
blockSizes[i] = blockSize;
validateMultipleFiles(code, fileSys, sourceDir, stripeLength, fileSizes,
blockSizes, singleFileBlockSize);
}
//
// creates a file by grouping multiple files together
// Returns its crc.
//
private long createDirectoryFile(FileSystem fileSys, Path name, int repl,
long[] fileSizes, long[] blockSizes, int[] seeds,
long blockSize) throws IOException {
CRC32 crc = new CRC32();
assert fileSizes.length == blockSizes.length;
assert fileSizes.length == seeds.length;
FSDataOutputStream stm = fileSys.create(name, true,
fileSys.getConf().getInt("io.file.buffer.size", 4096),
(short)repl, blockSize);
byte[] zeros = new byte[(int)(blockSize)];
for (int j = 0; j < zeros.length; j++) {
zeros[j] = 0;
}
// fill random data into file
for (int i = 0; i < fileSizes.length; i++) {
assert blockSizes[i] <= blockSize;
byte[] b = new byte[(int)blockSizes[i]];
long numBlocks = fileSizes[i] / blockSizes[i];
Random rand = new Random(seeds[i]);
for (int j = 0; j < numBlocks; j++) {
rand.nextBytes(b);
stm.write(b);
crc.update(b);
int zeroLen = (int)(blockSize - blockSizes[i]);
stm.write(zeros, 0, zeroLen);
crc.update(zeros, 0, zeroLen);
}
long lastBlock = fileSizes[i] - numBlocks * blockSizes[i];
if (lastBlock > 0) {
b = new byte[(int)lastBlock];
rand.nextBytes(b);
stm.write(b);
crc.update(b);
if (i + 1 < fileSizes.length) {
// Not last block of file, write zero
int zeroLen = (int)(blockSize - lastBlock);
stm.write(zeros, 0, zeroLen);
crc.update(zeros, 0, zeroLen);
}
}
}
stm.close();
return crc.getValue();
}
private void printFileCRC(FileSystem fs, Path file, long bufferSize)
throws IOException {
byte[] buffer = new byte[(int)bufferSize];
FSDataInputStream stm = fs.open(file);
StringBuilder sb = new StringBuilder();
sb.append("CRC for file: " + file + " size " +
fs.getFileStatus(file).getLen() + "\n");
while (stm.read(buffer) >= 0) {
CRC32 crc = new CRC32();
crc.update(buffer);
sb.append(" " + crc.getValue());
}
sb.append("\n");
System.out.println(sb.toString());
stm.close();
}
private void validateMultipleFiles(String code, FileSystem fileSys,
Path sourceDir, int stripeLength, long[] fileSizes, long[] blockSizes,
long blockSize) throws Exception {
assert fileSizes.length == blockSizes.length;
Codec codec = loadTestCodecs(code, stripeLength, true);
Path parityDir = new Path(codec.parityDirectory);
RaidDFSUtil.cleanUp(fileSys, sourceDir);
RaidDFSUtil.cleanUp(fileSys, parityDir);
fileSys.mkdirs(sourceDir);
LOG.info("Create files under directory " + sourceDir);
Random rand = new Random();
int[] seeds = new int[fileSizes.length];
for (int i = 0; i < fileSizes.length; i++) {
Path file = new Path(sourceDir, "file" + i);
seeds[i] = rand.nextInt();
TestRaidDfs.createTestFile(fileSys, file, 2, fileSizes[i],
blockSizes[i], seeds[i]);
}
Path parityFile = RaidNode.getOriginalParityFile(parityDir, sourceDir);
// Do directory level raid
LOG.info("Create a directory-raid parity file " + parityFile);
assertTrue("Cannot raid directory " + sourceDir,
doRaid(conf, fileSys, sourceDir, codec));
this.printFileCRC(fileSys, parityFile, blockSize);
long dirCRC = RaidDFSUtil.getCRC(fileSys, parityFile);
long dirLen = fileSys.getFileStatus(parityFile).getLen();
assertEquals("Modification time should be the same",
fileSys.getFileStatus(sourceDir).getModificationTime(),
fileSys.getFileStatus(parityFile).getModificationTime());
assertEquals("Replica num of parity file should be reduced to 1",
fileSys.getFileStatus(parityFile).getReplication(), 1);
for (int i = 0; i < fileSizes.length; i++) {
Path file = new Path(sourceDir, "file" + i);
assertEquals("Replica num of source file should be reduced to 1",
fileSys.getFileStatus(file).getReplication(), 1);
}
// remove the source dir and parity dir
RaidDFSUtil.cleanUp(fileSys, sourceDir);
RaidDFSUtil.cleanUp(fileSys, parityDir);
fileSys.mkdirs(sourceDir);
codec = loadTestCodecs(code, stripeLength, false);
Path file1 = new Path(sourceDir, "file1");
Path parityFile1 = RaidNode.getOriginalParityFile(parityDir,
file1);
LOG.info("Create a source file " + file1);
this.createDirectoryFile(fileSys, file1, 1, fileSizes, blockSizes, seeds,
blockSize);
LOG.info("Create a file-raid parity file " + parityFile1);
assertTrue("Cannot raid file " + file1,
doRaid(conf, fileSys, file1, codec));
this.printFileCRC(fileSys, parityFile1, blockSize);
assertTrue("Parity file doesn't match",
TestRaidDfs.validateFile(fileSys, parityFile1, dirLen, dirCRC));
}
public void testSmallFileDirectory() throws Exception {
mySetup();
int stripeLength = 4;
long blockSize = 8192L;
try {
for (String code: RaidDFSUtil.codes) {
LOG.info("testSmallFileDirectory: Test code " + code);
Path sourceDir = new Path("/user/raid");
validateMultipleFiles(code, fileSys, sourceDir, stripeLength,
new long[]{1000L, 4000L, 1000L}, blockSize, 4096L);
validateMultipleFiles(code, fileSys, sourceDir, stripeLength,
new long[]{2000L, 3000L, 2000L, 3000L}, blockSize, 3072L);
validateMultipleFiles(code, fileSys, sourceDir, stripeLength,
new long[]{3000L, 3000L, 3000L, 3000L}, blockSize, 3072L);
validateMultipleFiles(code, fileSys, sourceDir, stripeLength,
new long[]{511L, 3584L, 3000L, 1234L, 512L, 1234L, 3000L,
3234L, 511L}, blockSize, 3584L);
}
} finally {
myTearDown();
}
}
public void testIdenticalBlockSizeFileDirectory() throws Exception {
mySetup();
int stripeLength = 4;
long blockSize = 8192L;
try {
for (String code: RaidDFSUtil.codes) {
LOG.info("testIdenticalBlockSizeFileDirectory: Test code " + code);
Path sourceDir = new Path("/user/raid");
validateMultipleFiles(code, fileSys, sourceDir, stripeLength,
new long[] {1000L, blockSize, 2*blockSize, 4000L}, blockSize,
blockSize);
validateMultipleFiles(code, fileSys, sourceDir, stripeLength,
new long[] {blockSize, 2*blockSize, 3*blockSize, 4*blockSize},
blockSize, blockSize);
int halfBlock = (int)blockSize/2;
validateMultipleFiles(code, fileSys, sourceDir, stripeLength,
new long[] {blockSize + halfBlock, 2*blockSize + halfBlock,
3*blockSize + halfBlock, 4*blockSize + halfBlock},
blockSize, blockSize);
validateMultipleFiles(code, fileSys, sourceDir, stripeLength,
new long[] {blockSize+1, 9*blockSize+1, 2*blockSize+1,
3*blockSize+1}, blockSize, blockSize);
}
} finally {
myTearDown();
}
}
public void testDifferentBlockSizeFileDirectory() throws Exception {
mySetup();
int stripeLength = 3;
long blockSize = 8192L;
try {
for (String code: RaidDFSUtil.codes) {
LOG.info("testDifferentBlockSizeFileDirectory: Test code " + code);
Path sourceDir = new Path("/user/raid");
validateMultipleFiles(code, fileSys, sourceDir, stripeLength,
new long[] {1000, blockSize, 2*blockSize, 2*blockSize + 1},
new long[] {blockSize, blockSize, 2*blockSize, blockSize},
2*blockSize);
validateMultipleFiles(code, fileSys, sourceDir, stripeLength,
new long[] {blockSize, 2*blockSize, 3*blockSize, 4*blockSize},
new long[] {blockSize, 2*blockSize, 3*blockSize, blockSize},
3*blockSize);
validateMultipleFiles(code, fileSys, sourceDir, stripeLength,
new long[] {blockSize+1, 9*blockSize+1, 2*blockSize+1,
blockSize+1}, new long[]{blockSize, 2*blockSize, 3*blockSize,
blockSize}, 2*blockSize+512);
}
} finally {
myTearDown();
}
}
}