Skip to content
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,14 @@ public enum ChecksumCombineMode {
tags = ConfigTag.CLIENT)
private int ecReconstructStripeWritePoolLimit = 10 * 3;

@Config(key="ec.reconstruction.validation",
defaultValue = "false",
description = "Flag to enable validation for EC reconstruction tasks" +
" to reconstruct target containers correctly. Reconstruction tasks" +
" will fail if validation fails when enabled.",
tags = ConfigTag.CLIENT)
private boolean ecReconstructionValidation = false;

@Config(key = "checksum.combine.mode",
defaultValue = "COMPOSITE_CRC",
description = "The combined checksum type [MD5MD5CRC / COMPOSITE_CRC] "
Expand Down Expand Up @@ -509,6 +517,14 @@ public int getEcReconstructStripeWritePoolLimit() {
return ecReconstructStripeWritePoolLimit;
}

public void setEcReconstructionValidation(boolean validationEnabled) {
this.ecReconstructionValidation = validationEnabled;
}

public boolean getEcReconstructionValidation() {
return ecReconstructionValidation;
}

public void setFsDefaultBucketLayout(String bucketLayout) {
if (!bucketLayout.isEmpty()) {
this.fsDefaultBucketLayout = bucketLayout;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -825,5 +825,4 @@ private static SortedSet<Integer> setOfRange(
return range(startInclusive, endExclusive)
.boxed().collect(toCollection(TreeSet::new));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import org.apache.hadoop.ozone.client.io.BlockInputStreamFactoryImpl;
import org.apache.hadoop.ozone.client.io.ECBlockInputStreamProxy;
import org.apache.hadoop.ozone.client.io.ECBlockReconstructedStripeInputStream;
import org.apache.hadoop.ozone.common.ChunkBuffer;
import org.apache.hadoop.ozone.container.common.helpers.BlockData;
import org.apache.hadoop.ozone.container.common.statemachine.StateContext;
import org.apache.hadoop.security.token.Token;
Expand Down Expand Up @@ -115,6 +116,7 @@ public class ECReconstructionCoordinator implements Closeable {
private final ECReconstructionMetrics metrics;
private final StateContext context;
private final OzoneClientConfig ozoneClientConfig;
private final ECValidator ecValidator;

public ECReconstructionCoordinator(
ConfigurationSource conf, CertificateClient certificateClient,
Expand All @@ -140,6 +142,7 @@ public ECReconstructionCoordinator(
tokenHelper = new TokenHelper(new SecurityConfig(conf), secretKeyClient);
this.clientMetrics = ContainerClientMetrics.acquire();
this.metrics = metrics;
ecValidator = new ECValidator(ozoneClientConfig);
}

public void reconstructECContainerGroup(long containerID,
Expand Down Expand Up @@ -267,6 +270,7 @@ public void reconstructECBlockGroup(BlockLocationInfo blockLocationInfo,
this.blockInputStreamFactory, byteBufferPool,
this.ecReconstructReadExecutor,
clientConfig)) {
ecValidator.setBlockLength(blockLocationInfo.getLength());

ECBlockOutputStream[] targetBlockStreams =
new ECBlockOutputStream[toReconstructIndexes.size()];
Expand All @@ -291,8 +295,10 @@ public void reconstructECBlockGroup(BlockLocationInfo blockLocationInfo,
}

if (toReconstructIndexes.size() > 0) {
sis.setRecoveryIndexes(toReconstructIndexes.stream().map(i -> (i - 1))
.collect(Collectors.toSet()));
Set<Integer> recoveryIndexes = toReconstructIndexes.stream().map(i -> (i - 1))
.collect(Collectors.toSet());
sis.setRecoveryIndexes(recoveryIndexes);
ecValidator.setReconstructionIndexes(recoveryIndexes);
long length = safeBlockGroupLength;
while (length > 0) {
int readLen;
Expand Down Expand Up @@ -337,6 +343,7 @@ public void reconstructECBlockGroup(BlockLocationInfo blockLocationInfo,
List<ECBlockOutputStream> allStreams = new ArrayList<>(Arrays.asList(targetBlockStreams));
allStreams.addAll(Arrays.asList(emptyBlockStreams));
for (ECBlockOutputStream targetStream : allStreams) {
ecValidator.validateChecksum(targetStream, blockDataGroup);
targetStream.executePutBlock(true, true, blockLocationInfo.getLength(), blockDataGroup);
checkFailures(targetStream, targetStream.getCurrentPutBlkResponseFuture());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package org.apache.hadoop.ozone.container.ec.reconstruction;

import org.apache.hadoop.hdds.client.ECReplicationConfig;
import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos;
import org.apache.hadoop.hdds.scm.OzoneClientConfig;
import org.apache.hadoop.hdds.scm.storage.ECBlockOutputStream;
import org.apache.hadoop.ozone.common.Checksum;
import org.apache.hadoop.ozone.common.ChecksumData;
import org.apache.hadoop.ozone.common.ChunkBuffer;
import org.apache.hadoop.ozone.common.OzoneChecksumException;
import org.apache.hadoop.ozone.container.common.helpers.BlockData;
import org.apache.hadoop.ozone.container.common.helpers.ChunkInfo;
import org.apache.hadoop.ozone.container.common.interfaces.Container;
import org.apache.ratis.thirdparty.com.google.protobuf.ByteString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.ByteBuffer;
import java.util.*;
import java.util.stream.Collectors;

public class ECValidator {

private static final Logger LOG =
LoggerFactory.getLogger(ECValidator.class);
private final boolean isValidationEnabled;
private Collection<Integer> reconstructionIndexes;
private final int parityCount;
private long blockLength;
private final ECReplicationConfig ecReplicationConfig;

ECValidator(OzoneClientConfig config, ECReplicationConfig ecReplConfig) {
// We fetch the configuration value beforehand to avoid re-fetching on every validation call
isValidationEnabled = config.getEcReconstructionValidation();
ecReplicationConfig = ecReplConfig;
parityCount = ecReplConfig.getParity();
}

public void setReconstructionIndexes(Collection<Integer> reconstructionIndexes) {
this.reconstructionIndexes = reconstructionIndexes;
}

public void setBlockLength(long blockLength) {
this.blockLength = blockLength;
}

private void validateChecksumInStripe(ContainerProtos.ChecksumData checksumData,
ByteString stripeChecksum, int chunkIndex)
throws OzoneChecksumException {

// If we have say 100 bytes per checksum, in the stripe the first 100 bytes should
// correspond to the fist chunk checksum, next 100 should be the second chunk checksum

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A chunk can a multiple checksum depending on the size of the chunk and bytesPerCrc.
For example, If we have EC 3-2-1024k. We have 1 MB chunk, The calculation would be correct if the bytesPerCrc is also 1MB. ButbytesPerCrc is configurable. But by default #6331 changes this value to 16KB. Which means we would have (1024/16) = 16 checksums for each chunk. We need to take that into account as well.

You can take a look at #7230 I have added changes to split the stripeChecksum into parts. But the core idea is the one I mentioned above.

// and so on. So the checksum should range from (numOfBytes * index of chunk) to ((numOfBytes * index of chunk) + numOfBytes)
int bytesPerChecksum = checksumData.getBytesPerChecksum();

int checksumIdxStart = (bytesPerChecksum * chunkIndex);
ByteString expectedChecksum = stripeChecksum.substring(checksumIdxStart,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of ByteString and substring and we can use ByteBuffer for fine grained byte level buffer manipulation. ECBlockChecksumComputer#computeCompositeCrc() has similar implementation for this.

(checksumIdxStart + bytesPerChecksum));
if (!checksumData.getChecksums(0).equals(expectedChecksum)) {
throw new OzoneChecksumException(String.format("Mismatch in checksum for recreated data: %s and existing stripe checksum: %s",
checksumData.getChecksums(0), expectedChecksum));
}
}

private BlockData getChecksumBlockData(BlockData[] blockDataGroup) {
BlockData checksumBlockData = null;
// Reverse traversal as all parity bits will have checksumBytes
for (int i = blockDataGroup.length - 1; i >= 0; i--) {
BlockData blockData = blockDataGroup[i];
if (null == blockData) {
continue;
}

List<ContainerProtos.ChunkInfo> chunks = blockData.getChunks();
if (null != chunks && !(chunks.isEmpty())) {
if (chunks.get(0).hasStripeChecksum()) {
checksumBlockData = blockData;
break;
}
}
}

return checksumBlockData;
}

/**
* Helper function to validate the checksum between recreated data and
* @param ecBlockOutputStream A {@link ECBlockOutputStream} instance that stores
* the reconstructed index ECBlockOutputStream
* @throws OzoneChecksumException if the recreated checksum and the block checksum doesn't match
*/
public void validateChecksum(ECBlockOutputStream ecBlockOutputStream, BlockData[] blockDataGroup)
throws OzoneChecksumException{
if (isValidationEnabled) {

//Checksum will be stored in the 1st chunk and parity chunks
List<ContainerProtos.ChunkInfo> recreatedChunks = ecBlockOutputStream.getContainerBlockData().getChunksList();
BlockData checksumBlockData = getChecksumBlockData(blockDataGroup);
if (null == checksumBlockData) {
throw new OzoneChecksumException("Could not find checksum data in any index for blockDataGroup while validating");
}
List<ContainerProtos.ChunkInfo> checksumBlockChunks = checksumBlockData.getChunks();

for (int i = 0; i < recreatedChunks.size(); i++) {
validateChecksumInStripe(
recreatedChunks.get(i).getChecksumData(),
checksumBlockChunks.get(i).getStripeChecksum(), i
);
}
} else {
LOG.debug("Checksum validation was disabled, skipping check");
}
}
}