HADOOP-11921. Enhance tests for erasure coders. Contributed by Kai Zheng.

This commit is contained in:
Zhe Zhang 2015-05-18 10:06:56 -07:00 committed by Zhe Zhang
parent a919726914
commit 9c7a78c874
9 changed files with 277 additions and 154 deletions

View File

@ -44,3 +44,5 @@
HADOOP-11818. Minor improvements for erasurecode classes. (Rakesh R via Kai Zheng) HADOOP-11818. Minor improvements for erasurecode classes. (Rakesh R via Kai Zheng)
HADOOP-11841. Remove unused ecschema-def.xml files. (szetszwo) HADOOP-11841. Remove unused ecschema-def.xml files. (szetszwo)
HADOOP-11921. Enhance tests for erasure coders. (Kai Zheng via Zhe Zhang)

View File

@ -49,15 +49,15 @@ public abstract class TestCoderBase {
* Prepare before running the case. * Prepare before running the case.
* @param numDataUnits * @param numDataUnits
* @param numParityUnits * @param numParityUnits
* @param erasedIndexes * @param erasedDataIndexes
*/ */
protected void prepare(Configuration conf, int numDataUnits, protected void prepare(Configuration conf, int numDataUnits,
int numParityUnits, int[] erasedIndexes) { int numParityUnits, int[] erasedDataIndexes) {
this.conf = conf; this.conf = conf;
this.numDataUnits = numDataUnits; this.numDataUnits = numDataUnits;
this.numParityUnits = numParityUnits; this.numParityUnits = numParityUnits;
this.erasedDataIndexes = erasedIndexes != null ? this.erasedDataIndexes = erasedDataIndexes != null ?
erasedIndexes : new int[] {0}; erasedDataIndexes : new int[] {0};
} }
/** /**
@ -82,15 +82,19 @@ public abstract class TestCoderBase {
} }
/** /**
* Adjust and return erased indexes based on the array of the input chunks ( * Adjust and return erased indexes altogether, including erased data indexes
* parity chunks + data chunks). * and parity indexes.
* @return * @return erased indexes altogether
*/ */
protected int[] getErasedIndexesForDecoding() { protected int[] getErasedIndexesForDecoding() {
int[] erasedIndexesForDecoding = new int[erasedDataIndexes.length]; int[] erasedIndexesForDecoding = new int[erasedDataIndexes.length];
int idx = 0;
for (int i = 0; i < erasedDataIndexes.length; i++) { for (int i = 0; i < erasedDataIndexes.length; i++) {
erasedIndexesForDecoding[i] = erasedDataIndexes[i] + numParityUnits; erasedIndexesForDecoding[idx ++] = erasedDataIndexes[i] + numParityUnits;
} }
return erasedIndexesForDecoding; return erasedIndexesForDecoding;
} }
@ -116,30 +120,23 @@ public abstract class TestCoderBase {
} }
/** /**
* Have a copy of the data chunks that's to be erased thereafter. The copy * Erase chunks to test the recovering of them. Before erasure clone them
* will be used to compare and verify with the to be recovered chunks. * first so could return them.
* @param dataChunks * @param dataChunks
* @return * @return clone of erased chunks
*/ */
protected ECChunk[] copyDataChunksToErase(ECChunk[] dataChunks) { protected ECChunk[] backupAndEraseChunks(ECChunk[] dataChunks) {
ECChunk[] copiedChunks = new ECChunk[erasedDataIndexes.length]; ECChunk[] toEraseChunks = new ECChunk[erasedDataIndexes.length];
int idx = 0;
int j = 0;
for (int i = 0; i < erasedDataIndexes.length; i++) { for (int i = 0; i < erasedDataIndexes.length; i++) {
copiedChunks[j ++] = cloneChunkWithData(dataChunks[erasedDataIndexes[i]]); ECChunk chunk = dataChunks[erasedDataIndexes[i]];
toEraseChunks[idx ++] = cloneChunkWithData(chunk);
eraseDataFromChunk(chunk);
} }
return copiedChunks; return toEraseChunks;
}
/**
* Erase some data chunks to test the recovering of them
* @param dataChunks
*/
protected void eraseSomeDataBlocks(ECChunk[] dataChunks) {
for (int i = 0; i < erasedDataIndexes.length; i++) {
eraseDataFromChunk(dataChunks[erasedDataIndexes[i]]);
}
} }
/** /**
@ -277,6 +274,7 @@ public abstract class TestCoderBase {
*/ */
protected ECChunk[] prepareOutputChunksForDecoding() { protected ECChunk[] prepareOutputChunksForDecoding() {
ECChunk[] chunks = new ECChunk[erasedDataIndexes.length]; ECChunk[] chunks = new ECChunk[erasedDataIndexes.length];
for (int i = 0; i < chunks.length; i++) { for (int i = 0; i < chunks.length; i++) {
chunks[i] = allocateOutputChunk(); chunks[i] = allocateOutputChunk();
} }

View File

@ -29,6 +29,9 @@ public abstract class TestErasureCoderBase extends TestCoderBase {
protected Class<? extends ErasureCoder> encoderClass; protected Class<? extends ErasureCoder> encoderClass;
protected Class<? extends ErasureCoder> decoderClass; protected Class<? extends ErasureCoder> decoderClass;
private ErasureCoder encoder;
private ErasureCoder decoder;
protected int numChunksInBlock = 16; protected int numChunksInBlock = 16;
/** /**
@ -54,39 +57,27 @@ public abstract class TestErasureCoderBase extends TestCoderBase {
*/ */
protected void testCoding(boolean usingDirectBuffer) { protected void testCoding(boolean usingDirectBuffer) {
this.usingDirectBuffer = usingDirectBuffer; this.usingDirectBuffer = usingDirectBuffer;
prepareCoders();
ErasureCoder encoder = createEncoder();
// Generate data and encode // Generate data and encode
ECBlockGroup blockGroup = prepareBlockGroupForEncoding(); ECBlockGroup blockGroup = prepareBlockGroupForEncoding();
// Backup all the source chunks for later recovering because some coders // Backup all the source chunks for later recovering because some coders
// may affect the source data. // may affect the source data.
TestBlock[] clonedDataBlocks = cloneBlocksWithData((TestBlock[]) TestBlock[] clonedDataBlocks = cloneBlocksWithData((TestBlock[]) blockGroup.getDataBlocks());
blockGroup.getDataBlocks());
// Make a copy of a strip for later comparing
TestBlock[] toEraseBlocks = copyDataBlocksToErase(clonedDataBlocks);
ErasureCodingStep codingStep; ErasureCodingStep codingStep;
try {
codingStep = encoder.calculateCoding(blockGroup); codingStep = encoder.calculateCoding(blockGroup);
performCodingStep(codingStep); performCodingStep(codingStep);
} finally { // Erase specified sources but return copies of them for later comparing
encoder.release(); TestBlock[] backupBlocks = backupAndEraseBlocks(clonedDataBlocks);
}
// Erase the copied sources
eraseSomeDataBlocks(clonedDataBlocks);
// Decode // Decode
blockGroup = new ECBlockGroup(clonedDataBlocks, blockGroup.getParityBlocks()); blockGroup = new ECBlockGroup(clonedDataBlocks, blockGroup.getParityBlocks());
ErasureCoder decoder = createDecoder();
try {
codingStep = decoder.calculateCoding(blockGroup); codingStep = decoder.calculateCoding(blockGroup);
performCodingStep(codingStep); performCodingStep(codingStep);
} finally {
decoder.release();
}
// Compare // Compare
compareAndVerify(toEraseBlocks, codingStep.getOutputBlocks()); compareAndVerify(backupBlocks, codingStep.getOutputBlocks());
} }
/** /**
@ -129,8 +120,7 @@ public abstract class TestErasureCoderBase extends TestCoderBase {
protected void compareAndVerify(ECBlock[] erasedBlocks, protected void compareAndVerify(ECBlock[] erasedBlocks,
ECBlock[] recoveredBlocks) { ECBlock[] recoveredBlocks) {
for (int i = 0; i < erasedBlocks.length; ++i) { for (int i = 0; i < erasedBlocks.length; ++i) {
compareAndVerify(((TestBlock) erasedBlocks[i]).chunks, compareAndVerify(((TestBlock) erasedBlocks[i]).chunks, ((TestBlock) recoveredBlocks[i]).chunks);
((TestBlock) recoveredBlocks[i]).chunks);
} }
} }
@ -151,6 +141,16 @@ public abstract class TestErasureCoderBase extends TestCoderBase {
return encoder; return encoder;
} }
private void prepareCoders() {
if (encoder == null) {
encoder = createEncoder();
}
if (decoder == null) {
decoder = createDecoder();
}
}
/** /**
* Create the erasure decoder for the test. * Create the erasure decoder for the test.
* @return * @return
@ -201,6 +201,26 @@ public abstract class TestErasureCoderBase extends TestCoderBase {
return new TestBlock(chunks); return new TestBlock(chunks);
} }
/**
* Erase blocks to test the recovering of them. Before erasure clone them
* first so could return themselves.
* @param dataBlocks
* @return clone of erased dataBlocks
*/
protected TestBlock[] backupAndEraseBlocks(TestBlock[] dataBlocks) {
TestBlock[] toEraseBlocks = new TestBlock[erasedDataIndexes.length];
int idx = 0;
for (int i = 0; i < erasedDataIndexes.length; i++) {
TestBlock block = dataBlocks[erasedDataIndexes[i]];
toEraseBlocks[idx ++] = cloneBlockWithData(block);
eraseDataFromBlock(block);
}
return toEraseBlocks;
}
/** /**
* Copy those data blocks that's to be erased for later comparing and * Copy those data blocks that's to be erased for later comparing and
* verifying. * verifying.
@ -255,22 +275,9 @@ public abstract class TestErasureCoderBase extends TestCoderBase {
} }
/** /**
* Erase some data blocks specified by the indexes from the data blocks. * Erase data from a block.
* @param dataBlocks
*/ */
protected void eraseSomeDataBlocks(TestBlock[] dataBlocks) { protected void eraseDataFromBlock(TestBlock theBlock) {
for (int i = 0; i < erasedDataIndexes.length; ++i) {
eraseDataFromBlock(dataBlocks, erasedDataIndexes[i]);
}
}
/**
* Erase data from a block specified by erased index.
* @param blocks
* @param erasedIndex
*/
protected void eraseDataFromBlock(TestBlock[] blocks, int erasedIndex) {
TestBlock theBlock = blocks[erasedIndex];
eraseDataFromChunks(theBlock.chunks); eraseDataFromChunks(theBlock.chunks);
theBlock.setErased(true); theBlock.setErased(true);
} }

View File

@ -40,19 +40,18 @@ public class TestRSErasureCoder extends TestErasureCoderBase {
} }
@Test @Test
public void testCodingNoDirectBuffer_10x4() { public void testCodingNoDirectBuffer_10x4_erasing_d0() {
prepare(null, 10, 4, null); prepare(null, 10, 4, new int[] {0});
/**
* Doing twice to test if the coders can be repeatedly reused. This matters
* as the underlying coding buffers are shared, which may have bugs.
*/
testCoding(false);
testCoding(false); testCoding(false);
} }
@Test @Test
public void testCodingDirectBuffer_10x4() { public void testCodingDirectBufferWithConf_10x4_erasing_d0() {
prepare(null, 10, 4, null);
testCoding(true);
}
@Test
public void testCodingDirectBufferWithConf_10x4() {
/** /**
* This tests if the two configuration items work or not. * This tests if the two configuration items work or not.
*/ */
@ -61,31 +60,62 @@ public class TestRSErasureCoder extends TestErasureCoderBase {
RSRawErasureCoderFactory.class.getCanonicalName()); RSRawErasureCoderFactory.class.getCanonicalName());
conf.setBoolean( conf.setBoolean(
CommonConfigurationKeys.IO_ERASURECODE_CODEC_RS_USEXOR_KEY, false); CommonConfigurationKeys.IO_ERASURECODE_CODEC_RS_USEXOR_KEY, false);
prepare(conf, 10, 4, null);
prepare(conf, 10, 4, new int[]{0});
testCoding(true); testCoding(true);
} }
@Test @Test
public void testCodingDirectBuffer_10x4_erasure_of_2_4() { public void testCodingDirectBuffer_10x4_erasing_d2() {
prepare(null, 10, 4, new int[] {2});
testCoding(true);
testCoding(true);
}
@Test
public void testCodingDirectBuffer_10x4_erasing_d0() {
prepare(null, 10, 4, new int[] {0});
testCoding(true);
testCoding(true);
}
@Test
public void testCodingBothBuffers_10x4_erasing_d0() {
prepare(null, 10, 4, new int[] {0});
/**
* Doing in mixed buffer usage model to test if the coders can be repeatedly
* reused with different buffer usage model. This matters as the underlying
* coding buffers are shared, which may have bugs.
*/
testCoding(true);
testCoding(false);
testCoding(true);
testCoding(false);
}
@Test
public void testCodingDirectBuffer_10x4_erasure_of_d2_d4() {
prepare(null, 10, 4, new int[] {2, 4}); prepare(null, 10, 4, new int[] {2, 4});
testCoding(true); testCoding(true);
} }
@Test @Test
public void testCodingDirectBuffer_10x4_erasing_all() { public void testCodingDirectBuffer_10x4_erasing_d0_d1() {
prepare(null, 10, 4, new int[] {0, 1, 2, 3}); prepare(null, 10, 4, new int[] {0, 1});
testCoding(true); testCoding(true);
} }
@Test @Test
public void testCodingNoDirectBuffer_3x3() { public void testCodingNoDirectBuffer_3x3_erasing_d0() {
prepare(null, 3, 3, null); prepare(null, 3, 3, new int[] {0});
testCoding(false); testCoding(false);
} }
@Test @Test
public void testCodingDirectBuffer_3x3() { public void testCodingDirectBuffer_3x3_erasing_d0() {
prepare(null, 3, 3, null); prepare(null, 3, 3, new int[] {0});
testCoding(true); testCoding(true);
} }

View File

@ -32,19 +32,33 @@ public class TestXORCoder extends TestErasureCoderBase {
this.numDataUnits = 10; this.numDataUnits = 10;
this.numParityUnits = 1; this.numParityUnits = 1;
this.erasedDataIndexes = new int[] {0};
this.numChunksInBlock = 10; this.numChunksInBlock = 10;
} }
@Test @Test
public void testCodingNoDirectBuffer() { public void testCodingNoDirectBuffer_erasing_d0() {
prepare(null, 10, 1, new int[] {0});
/**
* Doing twice to test if the coders can be repeatedly reused. This matters
* as the underlying coding buffers are shared, which may have bugs.
*/
testCoding(false);
testCoding(false); testCoding(false);
} }
@Test @Test
public void testCodingDirectBuffer() { public void testCodingBothBuffers_erasing_d5() {
testCoding(true); prepare(null, 10, 1, new int[]{5});
}
/**
* Doing in mixed buffer usage model to test if the coders can be repeatedly
* reused with different buffer usage model. This matters as the underlying
* coding buffers are shared, which may have bugs.
*/
testCoding(true);
testCoding(false);
testCoding(true);
testCoding(false);
}
} }

View File

@ -17,26 +17,13 @@
*/ */
package org.apache.hadoop.io.erasurecode.rawcoder; package org.apache.hadoop.io.erasurecode.rawcoder;
import org.apache.hadoop.io.erasurecode.ECChunk;
import org.apache.hadoop.io.erasurecode.rawcoder.util.RSUtil;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import java.nio.ByteBuffer;
/** /**
* Test raw Reed-solomon encoding and decoding. * Test raw Reed-solomon coder implemented in Java.
*/ */
public class TestRSRawCoder extends TestRawCoderBase { public class TestRSRawCoder extends TestRSRawCoderBase {
private static int symbolSize = 0;
private static int symbolMax = 0;
static {
symbolSize = (int) Math.round(Math.log(
RSUtil.GF.getFieldSize()) / Math.log(2));
symbolMax = (int) Math.pow(2, symbolSize);
}
@Before @Before
public void setup() { public void setup() {
@ -45,49 +32,66 @@ public class TestRSRawCoder extends TestRawCoderBase {
} }
@Test @Test
public void testCodingNoDirectBuffer_10x4() { public void testCodingNoDirectBuffer_10x4_erasing_d0() {
prepare(null, 10, 4, null); prepare(null, 10, 4, new int[] {0});
/**
* Doing twice to test if the coders can be repeatedly reused. This matters
* as the underlying coding buffers are shared, which may have bugs.
*/
testCoding(false);
testCoding(false); testCoding(false);
} }
@Test @Test
public void testCodingDirectBuffer_10x4() { public void testCodingDirectBuffer_10x4_erasing_d2() {
prepare(null, 10, 4, null); prepare(null, 10, 4, new int[] {2});
testCoding(true);
testCoding(true); testCoding(true);
} }
@Test @Test
public void testCodingDirectBuffer_10x4_erasure_of_2_4() { public void testCodingDirectBuffer_10x4_erasing_d0() {
prepare(null, 10, 4, new int[] {0});
testCoding(true);
testCoding(true);
}
@Test
public void testCodingBothBuffers_10x4_erasing_d0() {
prepare(null, 10, 4, new int[] {0});
/**
* Doing in mixed buffer usage model to test if the coders can be repeatedly
* reused with different buffer usage model. This matters as the underlying
* coding buffers are shared, which may have bugs.
*/
testCoding(true);
testCoding(false);
testCoding(true);
testCoding(false);
}
@Test
public void testCodingDirectBuffer_10x4_erasure_of_d2_d4() {
prepare(null, 10, 4, new int[] {2, 4}); prepare(null, 10, 4, new int[] {2, 4});
testCoding(true); testCoding(true);
} }
@Test @Test
public void testCodingDirectBuffer_10x4_erasing_all() { public void testCodingDirectBuffer_10x4_erasing_d0_d1() {
prepare(null, 10, 4, new int[] {0, 1, 2, 3}); prepare(null, 10, 4, new int[] {0, 1});
testCoding(true); testCoding(true);
} }
@Test @Test
public void testCodingNoDirectBuffer_3x3() { public void testCodingNoDirectBuffer_3x3_erasing_d0() {
prepare(null, 3, 3, null); prepare(null, 3, 3, new int[] {0});
testCoding(false); testCoding(false);
} }
@Test @Test
public void testCodingDirectBuffer_3x3() { public void testCodingDirectBuffer_3x3_erasing_d0() {
prepare(null, 3, 3, null); prepare(null, 3, 3, new int[] {0});
testCoding(true); testCoding(true);
} }
@Override
protected ECChunk generateDataChunk() {
ByteBuffer buffer = allocateOutputBuffer();
for (int i = 0; i < chunkSize; i++) {
buffer.put((byte) RAND.nextInt(symbolMax));
}
buffer.flip();
return new ECChunk(buffer);
}
} }

View File

@ -0,0 +1,51 @@
/**
* 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.io.erasurecode.rawcoder;
import org.apache.hadoop.io.erasurecode.ECChunk;
import org.apache.hadoop.io.erasurecode.rawcoder.util.RSUtil;
import org.junit.Before;
import org.junit.Test;
import java.nio.ByteBuffer;
/**
* Test base for raw Reed-solomon coders.
*/
public abstract class TestRSRawCoderBase extends TestRawCoderBase {
private static int symbolSize = 0;
private static int symbolMax = 0;
static {
symbolSize = (int) Math.round(Math.log(
RSUtil.GF.getFieldSize()) / Math.log(2));
symbolMax = (int) Math.pow(2, symbolSize);
}
@Override
protected ECChunk generateDataChunk() {
ByteBuffer buffer = allocateOutputBuffer();
for (int i = 0; i < chunkSize; i++) {
buffer.put((byte) RAND.nextInt(symbolMax));
}
buffer.flip();
return new ECChunk(buffer);
}
}

View File

@ -26,6 +26,8 @@ import org.apache.hadoop.io.erasurecode.TestCoderBase;
public abstract class TestRawCoderBase extends TestCoderBase { public abstract class TestRawCoderBase extends TestCoderBase {
protected Class<? extends RawErasureEncoder> encoderClass; protected Class<? extends RawErasureEncoder> encoderClass;
protected Class<? extends RawErasureDecoder> decoderClass; protected Class<? extends RawErasureDecoder> decoderClass;
private RawErasureEncoder encoder;
private RawErasureDecoder decoder;
/** /**
* Generating source data, encoding, recovering and then verifying. * Generating source data, encoding, recovering and then verifying.
@ -37,40 +39,41 @@ public abstract class TestRawCoderBase extends TestCoderBase {
*/ */
protected void testCoding(boolean usingDirectBuffer) { protected void testCoding(boolean usingDirectBuffer) {
this.usingDirectBuffer = usingDirectBuffer; this.usingDirectBuffer = usingDirectBuffer;
prepareCoders();
// Generate data and encode // Generate data and encode
ECChunk[] dataChunks = prepareDataChunksForEncoding(); ECChunk[] dataChunks = prepareDataChunksForEncoding();
ECChunk[] parityChunks = prepareParityChunksForEncoding(); ECChunk[] parityChunks = prepareParityChunksForEncoding();
RawErasureEncoder encoder = createEncoder();
// Backup all the source chunks for later recovering because some coders // Backup all the source chunks for later recovering because some coders
// may affect the source data. // may affect the source data.
ECChunk[] clonedDataChunks = cloneChunksWithData(dataChunks); ECChunk[] clonedDataChunks = cloneChunksWithData(dataChunks);
// Make a copy of a strip for later comparing
ECChunk[] toEraseDataChunks = copyDataChunksToErase(clonedDataChunks);
try {
encoder.encode(dataChunks, parityChunks); encoder.encode(dataChunks, parityChunks);
} finally {
encoder.release(); // Backup and erase some chunks
} ECChunk[] backupChunks = backupAndEraseChunks(clonedDataChunks);
// Erase the copied sources
eraseSomeDataBlocks(clonedDataChunks);
// Decode // Decode
ECChunk[] inputChunks = prepareInputChunksForDecoding(clonedDataChunks, ECChunk[] inputChunks = prepareInputChunksForDecoding(
parityChunks); clonedDataChunks, parityChunks);
ECChunk[] recoveredChunks = prepareOutputChunksForDecoding(); ECChunk[] recoveredChunks = prepareOutputChunksForDecoding();
RawErasureDecoder decoder = createDecoder();
try { decoder.decode(inputChunks, getErasedIndexesForDecoding(), recoveredChunks);
decoder.decode(inputChunks,
getErasedIndexesForDecoding(), recoveredChunks);
} finally {
decoder.release();
}
// Compare // Compare
compareAndVerify(toEraseDataChunks, recoveredChunks); compareAndVerify(backupChunks, recoveredChunks);
}
private void prepareCoders() {
if (encoder == null) {
encoder = createEncoder();
}
if (decoder == null) {
decoder = createDecoder();
}
} }
/** /**

View File

@ -32,18 +32,32 @@ public class TestXORRawCoder extends TestRawCoderBase {
this.numDataUnits = 10; this.numDataUnits = 10;
this.numParityUnits = 1; this.numParityUnits = 1;
this.erasedDataIndexes = new int[] {0};
} }
@Test @Test
public void testCodingNoDirectBuffer() { public void testCodingNoDirectBuffer_erasing_d0() {
prepare(null, 10, 1, new int[] {0});
/**
* Doing twice to test if the coders can be repeatedly reused. This matters
* as the underlying coding buffers are shared, which may have bugs.
*/
testCoding(false);
testCoding(false); testCoding(false);
} }
@Test @Test
public void testCodingDirectBuffer() { public void testCodingBothBuffers_erasing_d5() {
testCoding(true); prepare(null, 10, 1, new int[]{5});
}
/**
* Doing in mixed buffer usage model to test if the coders can be repeatedly
* reused with different buffer usage model. This matters as the underlying
* coding buffers are shared, which may have bugs.
*/
testCoding(true);
testCoding(false);
testCoding(true);
testCoding(false);
}
} }