diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1e3e07c5926d..e6964804f0db 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -135,13 +135,17 @@ jobs: - build-info - build - basic - runs-on: ubuntu-20.04 timeout-minutes: 30 if: needs.build-info.outputs.needs-compile == 'true' strategy: matrix: java: [ 11, 17 ] + include: + - os: ubuntu-20.04 + - java: 8 + os: macos-12 fail-fast: false + runs-on: ${{ matrix.os }} steps: - name: Download Ozone source tarball uses: actions/download-artifact@v4 diff --git a/dev-support/ci/selective_ci_checks.bats b/dev-support/ci/selective_ci_checks.bats index 243ae283c16d..150222de1ccc 100644 --- a/dev-support/ci/selective_ci_checks.bats +++ b/dev-support/ci/selective_ci_checks.bats @@ -177,17 +177,20 @@ load bats-assert/load.bash assert_output -p needs-kubernetes-tests=false } -@test "native test in other module" { - run dev-support/ci/selective_ci_checks.sh 7d01cc14a6 - - assert_output -p 'basic-checks=["rat","author","checkstyle","findbugs","native","unit"]' - assert_output -p needs-build=true - assert_output -p needs-compile=true - assert_output -p needs-compose-tests=false - assert_output -p needs-dependency-check=false - assert_output -p needs-integration-tests=false - assert_output -p needs-kubernetes-tests=false -} +# disabled, because this test fails if +# hadoop-hdds/rocksdb-checkpoint-differ/src/test/java/org/apache/ozone/rocksdb/util/TestManagedSstFileReader.java +# is not present in the current tree (i.e. if file is renamed, moved or deleted) +#@test "native test in other module" { +# run dev-support/ci/selective_ci_checks.sh 7d01cc14a6 +# +# assert_output -p 'basic-checks=["rat","author","checkstyle","findbugs","native","unit"]' +# assert_output -p needs-build=true +# assert_output -p needs-compile=true +# assert_output -p needs-compose-tests=false +# assert_output -p needs-dependency-check=false +# assert_output -p needs-integration-tests=false +# assert_output -p needs-kubernetes-tests=false +#} @test "kubernetes only" { run dev-support/ci/selective_ci_checks.sh 5336bb9bd diff --git a/hadoop-hdds/client/pom.xml b/hadoop-hdds/client/pom.xml index cfb1abb8830f..1564f86a7955 100644 --- a/hadoop-hdds/client/pom.xml +++ b/hadoop-hdds/client/pom.xml @@ -55,7 +55,7 @@ https://maven.apache.org/xsd/maven-4.0.0.xsd"> org.mockito - mockito-core + mockito-inline test diff --git a/hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/OzoneClientConfig.java b/hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/OzoneClientConfig.java index 3042b4d847a0..549735438a02 100644 --- a/hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/OzoneClientConfig.java +++ b/hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/OzoneClientConfig.java @@ -144,6 +144,23 @@ public enum ChecksumCombineMode { tags = ConfigTag.CLIENT) private int retryInterval = 0; + @Config(key = "read.max.retries", + defaultValue = "3", + description = "Maximum number of retries by Ozone Client on " + + "encountering connectivity exception when reading a key.", + tags = ConfigTag.CLIENT) + private int maxReadRetryCount = 3; + + @Config(key = "read.retry.interval", + defaultValue = "1", + description = + "Indicates the time duration in seconds a client will wait " + + "before retrying a read key request on encountering " + + "a connectivity excepetion from Datanodes . " + + "By default the interval is 1 second", + tags = ConfigTag.CLIENT) + private int readRetryInterval = 1; + @Config(key = "checksum.type", defaultValue = "CRC32", description = "The checksum type [NONE/ CRC32/ CRC32C/ SHA256/ MD5] " @@ -326,6 +343,22 @@ public void setRetryInterval(int retryInterval) { this.retryInterval = retryInterval; } + public int getMaxReadRetryCount() { + return maxReadRetryCount; + } + + public void setMaxReadRetryCount(int maxReadRetryCount) { + this.maxReadRetryCount = maxReadRetryCount; + } + + public int getReadRetryInterval() { + return readRetryInterval; + } + + public void setReadRetryInterval(int readRetryInterval) { + this.readRetryInterval = readRetryInterval; + } + public ChecksumType getChecksumType() { return ChecksumType.valueOf(checksumType); } diff --git a/hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/XceiverClientGrpc.java b/hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/XceiverClientGrpc.java index 0a38e6604897..6e2e1522a705 100644 --- a/hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/XceiverClientGrpc.java +++ b/hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/XceiverClientGrpc.java @@ -49,6 +49,7 @@ import org.apache.hadoop.hdds.security.exception.SCMSecurityException; import org.apache.hadoop.hdds.tracing.GrpcClientInterceptor; import org.apache.hadoop.hdds.tracing.TracingUtil; +import org.apache.hadoop.ozone.ClientVersion; import org.apache.hadoop.ozone.OzoneConfigKeys; import org.apache.hadoop.ozone.OzoneConsts; import java.util.concurrent.TimeoutException; @@ -274,6 +275,11 @@ public ContainerCommandResponseProto sendCommand( List datanodeList = pipeline.getNodes(); HashMap> futureHashMap = new HashMap<>(); + if (!request.hasVersion()) { + ContainerCommandRequestProto.Builder builder = ContainerCommandRequestProto.newBuilder(request); + builder.setVersion(ClientVersion.CURRENT.toProtoValue()); + request = builder.build(); + } for (DatanodeDetails dn : datanodeList) { try { futureHashMap.put(dn, sendCommandAsync(request, dn).getResponse()); @@ -334,10 +340,13 @@ private XceiverClientReply sendCommandWithTraceIDAndRetry( return TracingUtil.executeInNewSpan(spanName, () -> { - ContainerCommandRequestProto finalPayload = + ContainerCommandRequestProto.Builder builder = ContainerCommandRequestProto.newBuilder(request) - .setTraceID(TracingUtil.exportCurrentSpan()).build(); - return sendCommandWithRetry(finalPayload, validators); + .setTraceID(TracingUtil.exportCurrentSpan()); + if (!request.hasVersion()) { + builder.setVersion(ClientVersion.CURRENT.toProtoValue()); + } + return sendCommandWithRetry(builder.build(), validators); }); } @@ -457,12 +466,14 @@ public XceiverClientReply sendCommandAsync( try (Scope ignored = GlobalTracer.get().activateSpan(span)) { - ContainerCommandRequestProto finalPayload = + ContainerCommandRequestProto.Builder builder = ContainerCommandRequestProto.newBuilder(request) - .setTraceID(TracingUtil.exportCurrentSpan()) - .build(); + .setTraceID(TracingUtil.exportCurrentSpan()); + if (!request.hasVersion()) { + builder.setVersion(ClientVersion.CURRENT.toProtoValue()); + } XceiverClientReply asyncReply = - sendCommandAsync(finalPayload, pipeline.getFirstNode()); + sendCommandAsync(builder.build(), pipeline.getFirstNode()); if (shouldBlockAndWaitAsyncReply(request)) { asyncReply.getResponse().get(); } diff --git a/hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/storage/BlockInputStream.java b/hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/storage/BlockInputStream.java index 3d789912a66b..1c84507a8f90 100644 --- a/hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/storage/BlockInputStream.java +++ b/hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/storage/BlockInputStream.java @@ -36,6 +36,7 @@ import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.DatanodeBlockID; import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.GetBlockResponseProto; import org.apache.hadoop.hdds.protocol.proto.HddsProtos; +import org.apache.hadoop.hdds.scm.OzoneClientConfig; import org.apache.hadoop.hdds.scm.XceiverClientFactory; import org.apache.hadoop.hdds.scm.XceiverClientSpi; import org.apache.hadoop.hdds.scm.XceiverClientSpi.Validator; @@ -76,8 +77,8 @@ public class BlockInputStream extends BlockExtendedInputStream { private XceiverClientSpi xceiverClient; private boolean initialized = false; // TODO: do we need to change retrypolicy based on exception. - private final RetryPolicy retryPolicy = - HddsClientUtils.createRetryPolicy(3, TimeUnit.SECONDS.toMillis(1)); + private final RetryPolicy retryPolicy; + private int retries; // List of ChunkInputStreams, one for each chunk in the block @@ -112,25 +113,29 @@ public class BlockInputStream extends BlockExtendedInputStream { private final Function refreshFunction; public BlockInputStream(BlockID blockId, long blockLen, Pipeline pipeline, - Token token, boolean verifyChecksum, + Token token, XceiverClientFactory xceiverClientFactory, - Function refreshFunction) { + Function refreshFunction, + OzoneClientConfig config) throws IOException { this.blockID = blockId; this.length = blockLen; setPipeline(pipeline); tokenRef.set(token); - this.verifyChecksum = verifyChecksum; + this.verifyChecksum = config.isChecksumVerify(); this.xceiverClientFactory = xceiverClientFactory; this.refreshFunction = refreshFunction; + this.retryPolicy = + HddsClientUtils.createRetryPolicy(config.getMaxReadRetryCount(), + TimeUnit.SECONDS.toMillis(config.getReadRetryInterval())); } public BlockInputStream(BlockID blockId, long blockLen, Pipeline pipeline, Token token, - boolean verifyChecksum, - XceiverClientFactory xceiverClientFactory - ) { - this(blockId, blockLen, pipeline, token, verifyChecksum, - xceiverClientFactory, null); + XceiverClientFactory xceiverClientFactory, + OzoneClientConfig config + ) throws IOException { + this(blockId, blockLen, pipeline, token, + xceiverClientFactory, null, config); } /** * Initialize the BlockInputStream. Get the BlockData (list of chunks) from @@ -239,33 +244,28 @@ protected List getChunkInfoList() throws IOException { @VisibleForTesting protected List getChunkInfoListUsingClient() throws IOException { - final Pipeline pipeline = xceiverClient.getPipeline(); - + Pipeline pipeline = pipelineRef.get(); if (LOG.isDebugEnabled()) { - LOG.debug("Initializing BlockInputStream for get key to access {}", - blockID.getContainerID()); - } - - DatanodeBlockID.Builder blkIDBuilder = - DatanodeBlockID.newBuilder().setContainerID(blockID.getContainerID()) - .setLocalID(blockID.getLocalID()) - .setBlockCommitSequenceId(blockID.getBlockCommitSequenceId()); - - int replicaIndex = pipeline.getReplicaIndex(pipeline.getClosestNode()); - if (replicaIndex > 0) { - blkIDBuilder.setReplicaIndex(replicaIndex); + LOG.debug("Initializing BlockInputStream for get key to access {} with pipeline {}.", + blockID.getContainerID(), pipeline); } GetBlockResponseProto response = ContainerProtocolCalls.getBlock( - xceiverClient, VALIDATORS, blkIDBuilder.build(), tokenRef.get()); + xceiverClient, VALIDATORS, blockID, tokenRef.get(), pipeline.getReplicaIndexes()); return response.getBlockData().getChunksList(); } - private void setPipeline(Pipeline pipeline) { + private void setPipeline(Pipeline pipeline) throws IOException { if (pipeline == null) { return; } + long replicaIndexes = pipeline.getNodes().stream().mapToInt(pipeline::getReplicaIndex).distinct().count(); + + if (replicaIndexes > 1) { + throw new IOException(String.format("Pipeline: %s has nodes containing different replica indexes.", + pipeline)); + } // irrespective of the container state, we will always read via Standalone // protocol. diff --git a/hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/storage/ChunkInputStream.java b/hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/storage/ChunkInputStream.java index b30f555795b2..983bb74989ad 100644 --- a/hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/storage/ChunkInputStream.java +++ b/hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/storage/ChunkInputStream.java @@ -26,6 +26,8 @@ import org.apache.hadoop.fs.CanUnbuffer; import org.apache.hadoop.fs.Seekable; import org.apache.hadoop.hdds.client.BlockID; +import org.apache.hadoop.hdds.protocol.DatanodeDetails; +import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos; import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.ChunkInfo; import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.ContainerCommandRequestProto; import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.ContainerCommandResponseProto; @@ -60,6 +62,7 @@ public class ChunkInputStream extends InputStream private final ChunkInfo chunkInfo; private final long length; private final BlockID blockID; + private ContainerProtos.DatanodeBlockID datanodeBlockID; private final XceiverClientFactory xceiverClientFactory; private XceiverClientSpi xceiverClient; private final Supplier pipelineSupplier; @@ -290,13 +293,27 @@ protected synchronized void releaseClient() { } } + /** + * Updates DatanodeBlockId which based on blockId. + */ + private void updateDatanodeBlockId(Pipeline pipeline) throws IOException { + DatanodeDetails closestNode = pipeline.getClosestNode(); + int replicaIdx = pipeline.getReplicaIndex(closestNode); + ContainerProtos.DatanodeBlockID.Builder builder = blockID.getDatanodeBlockIDProtobufBuilder(); + if (replicaIdx > 0) { + builder.setReplicaIndex(replicaIdx); + } + datanodeBlockID = builder.build(); + } + /** * Acquire new client if previous one was released. */ protected synchronized void acquireClient() throws IOException { if (xceiverClientFactory != null && xceiverClient == null) { - xceiverClient = xceiverClientFactory.acquireClientForReadData( - pipelineSupplier.get()); + Pipeline pipeline = pipelineSupplier.get(); + xceiverClient = xceiverClientFactory.acquireClientForReadData(pipeline); + updateDatanodeBlockId(pipeline); } } @@ -422,8 +439,8 @@ protected ByteBuffer[] readChunk(ChunkInfo readChunkInfo) throws IOException { ReadChunkResponseProto readChunkResponse = - ContainerProtocolCalls.readChunk(xceiverClient, - readChunkInfo, blockID, validators, tokenSupplier.get()); + ContainerProtocolCalls.readChunk(xceiverClient, readChunkInfo, datanodeBlockID, validators, + tokenSupplier.get()); if (readChunkResponse.hasData()) { return readChunkResponse.getData().asReadOnlyByteBufferList() diff --git a/hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/storage/ECBlockOutputStream.java b/hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/storage/ECBlockOutputStream.java index ebcc37736ad0..f8214687648b 100644 --- a/hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/storage/ECBlockOutputStream.java +++ b/hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/storage/ECBlockOutputStream.java @@ -115,17 +115,28 @@ ContainerCommandResponseProto> executePutBlock(boolean close, } BlockData checksumBlockData = null; + BlockID blockID = null; //Reverse Traversal as all parity will have checksumBytes for (int i = blockData.length - 1; i >= 0; i--) { BlockData bd = blockData[i]; if (bd == null) { continue; } + if (blockID == null) { + // store the BlockID for logging + blockID = bd.getBlockID(); + } List chunks = bd.getChunks(); - if (chunks != null && chunks.size() > 0 && chunks.get(0) - .hasStripeChecksum()) { - checksumBlockData = bd; - break; + if (chunks != null && chunks.size() > 0) { + if (chunks.get(0).hasStripeChecksum()) { + checksumBlockData = bd; + break; + } else { + ChunkInfo chunk = chunks.get(0); + LOG.debug("The first chunk in block with index {} does not have stripeChecksum. BlockID: {}, Block " + + "size: {}. Chunk length: {}, Chunk offset: {}, hasChecksumData: {}, chunks size: {}.", i, + bd.getBlockID(), bd.getSize(), chunk.getLen(), chunk.getOffset(), chunk.hasChecksumData(), chunks.size()); + } } } @@ -158,9 +169,8 @@ ContainerCommandResponseProto> executePutBlock(boolean close, getContainerBlockData().clearChunks(); getContainerBlockData().addAllChunks(newChunkList); } else { - throw new IOException("None of the block data have checksum " + - "which means " + parity + "(parity)+1 blocks are " + - "not present"); + LOG.warn("Could not find checksum data in any index for blockData with BlockID {}, length {} and " + + "blockGroupLength {}.", blockID, blockData.length, blockGroupLength); } return executePutBlock(close, force, blockGroupLength); diff --git a/hadoop-hdds/client/src/main/java/org/apache/hadoop/ozone/client/io/BlockInputStreamFactory.java b/hadoop-hdds/client/src/main/java/org/apache/hadoop/ozone/client/io/BlockInputStreamFactory.java index bd100214ae48..d347dee85121 100644 --- a/hadoop-hdds/client/src/main/java/org/apache/hadoop/ozone/client/io/BlockInputStreamFactory.java +++ b/hadoop-hdds/client/src/main/java/org/apache/hadoop/ozone/client/io/BlockInputStreamFactory.java @@ -19,6 +19,7 @@ import org.apache.hadoop.hdds.client.BlockID; import org.apache.hadoop.hdds.client.ReplicationConfig; +import org.apache.hadoop.hdds.scm.OzoneClientConfig; import org.apache.hadoop.hdds.scm.XceiverClientFactory; import org.apache.hadoop.hdds.scm.pipeline.Pipeline; import org.apache.hadoop.hdds.scm.storage.BlockExtendedInputStream; @@ -26,6 +27,7 @@ import org.apache.hadoop.hdds.security.token.OzoneBlockTokenIdentifier; import org.apache.hadoop.security.token.Token; +import java.io.IOException; import java.util.function.Function; /** @@ -48,8 +50,9 @@ public interface BlockInputStreamFactory { */ BlockExtendedInputStream create(ReplicationConfig repConfig, BlockLocationInfo blockInfo, Pipeline pipeline, - Token token, boolean verifyChecksum, + Token token, XceiverClientFactory xceiverFactory, - Function refreshFunction); + Function refreshFunction, + OzoneClientConfig config) throws IOException; } diff --git a/hadoop-hdds/client/src/main/java/org/apache/hadoop/ozone/client/io/BlockInputStreamFactoryImpl.java b/hadoop-hdds/client/src/main/java/org/apache/hadoop/ozone/client/io/BlockInputStreamFactoryImpl.java index 40063f9ce492..19ea76fa7064 100644 --- a/hadoop-hdds/client/src/main/java/org/apache/hadoop/ozone/client/io/BlockInputStreamFactoryImpl.java +++ b/hadoop-hdds/client/src/main/java/org/apache/hadoop/ozone/client/io/BlockInputStreamFactoryImpl.java @@ -21,6 +21,7 @@ import org.apache.hadoop.hdds.client.ECReplicationConfig; import org.apache.hadoop.hdds.client.ReplicationConfig; import org.apache.hadoop.hdds.protocol.proto.HddsProtos; +import org.apache.hadoop.hdds.scm.OzoneClientConfig; import org.apache.hadoop.hdds.scm.XceiverClientFactory; import org.apache.hadoop.hdds.scm.pipeline.Pipeline; import org.apache.hadoop.hdds.scm.storage.BlockExtendedInputStream; @@ -31,6 +32,7 @@ import org.apache.hadoop.io.ElasticByteBufferPool; import org.apache.hadoop.security.token.Token; +import java.io.IOException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.function.Function; @@ -76,16 +78,18 @@ public BlockInputStreamFactoryImpl(ByteBufferPool byteBufferPool, */ public BlockExtendedInputStream create(ReplicationConfig repConfig, BlockLocationInfo blockInfo, Pipeline pipeline, - Token token, boolean verifyChecksum, + Token token, XceiverClientFactory xceiverFactory, - Function refreshFunction) { + Function refreshFunction, + OzoneClientConfig config) throws IOException { if (repConfig.getReplicationType().equals(HddsProtos.ReplicationType.EC)) { return new ECBlockInputStreamProxy((ECReplicationConfig)repConfig, - blockInfo, verifyChecksum, xceiverFactory, refreshFunction, - ecBlockStreamFactory); + blockInfo, xceiverFactory, refreshFunction, + ecBlockStreamFactory, config); } else { return new BlockInputStream(blockInfo.getBlockID(), blockInfo.getLength(), - pipeline, token, verifyChecksum, xceiverFactory, refreshFunction); + pipeline, token, xceiverFactory, refreshFunction, + config); } } diff --git a/hadoop-hdds/client/src/main/java/org/apache/hadoop/ozone/client/io/ECBlockInputStream.java b/hadoop-hdds/client/src/main/java/org/apache/hadoop/ozone/client/io/ECBlockInputStream.java index ea4f3d743f92..2f662e0f747a 100644 --- a/hadoop-hdds/client/src/main/java/org/apache/hadoop/ozone/client/io/ECBlockInputStream.java +++ b/hadoop-hdds/client/src/main/java/org/apache/hadoop/ozone/client/io/ECBlockInputStream.java @@ -26,6 +26,7 @@ import org.apache.hadoop.hdds.client.StandaloneReplicationConfig; import org.apache.hadoop.hdds.protocol.DatanodeDetails; import org.apache.hadoop.hdds.protocol.proto.HddsProtos; +import org.apache.hadoop.hdds.scm.OzoneClientConfig; import org.apache.hadoop.hdds.scm.XceiverClientFactory; import org.apache.hadoop.hdds.scm.pipeline.Pipeline; import org.apache.hadoop.hdds.scm.pipeline.PipelineID; @@ -60,7 +61,6 @@ public class ECBlockInputStream extends BlockExtendedInputStream { private final int ecChunkSize; private final long stripeSize; private final BlockInputStreamFactory streamFactory; - private final boolean verifyChecksum; private final XceiverClientFactory xceiverClientFactory; private final Function refreshFunction; private final BlockLocationInfo blockInfo; @@ -75,7 +75,7 @@ public class ECBlockInputStream extends BlockExtendedInputStream { private long position = 0; private boolean closed = false; private boolean seeked = false; - + private OzoneClientConfig config; protected ECReplicationConfig getRepConfig() { return repConfig; } @@ -119,13 +119,13 @@ protected int availableParityLocations() { } public ECBlockInputStream(ECReplicationConfig repConfig, - BlockLocationInfo blockInfo, boolean verifyChecksum, + BlockLocationInfo blockInfo, XceiverClientFactory xceiverClientFactory, Function refreshFunction, - BlockInputStreamFactory streamFactory) { + BlockInputStreamFactory streamFactory, + OzoneClientConfig config) { this.repConfig = repConfig; this.ecChunkSize = repConfig.getEcChunkSize(); - this.verifyChecksum = verifyChecksum; this.blockInfo = blockInfo; this.streamFactory = streamFactory; this.xceiverClientFactory = xceiverClientFactory; @@ -134,6 +134,7 @@ public ECBlockInputStream(ECReplicationConfig repConfig, this.dataLocations = new DatanodeDetails[repConfig.getRequiredNodes()]; this.blockStreams = new BlockExtendedInputStream[repConfig.getRequiredNodes()]; + this.config = config; this.stripeSize = (long)ecChunkSize * repConfig.getData(); setBlockLocations(this.blockInfo.getPipeline()); @@ -174,7 +175,7 @@ protected int currentStreamIndex() { * stream if it has not been opened already. * @return BlockInput stream to read from. */ - protected BlockExtendedInputStream getOrOpenStream(int locationIndex) { + protected BlockExtendedInputStream getOrOpenStream(int locationIndex) throws IOException { BlockExtendedInputStream stream = blockStreams[locationIndex]; if (stream == null) { // To read an EC block, we create a STANDALONE pipeline that contains the @@ -186,8 +187,8 @@ protected BlockExtendedInputStream getOrOpenStream(int locationIndex) { .setReplicationConfig(StandaloneReplicationConfig.getInstance( HddsProtos.ReplicationFactor.ONE)) .setNodes(Arrays.asList(dataLocation)) - .setId(PipelineID.valueOf(dataLocation.getUuid())).setReplicaIndexes( - ImmutableMap.of(dataLocation, locationIndex + 1)) + .setId(PipelineID.valueOf(dataLocation.getUuid())) + .setReplicaIndexes(ImmutableMap.of(dataLocation, locationIndex + 1)) .setState(Pipeline.PipelineState.CLOSED) .build(); @@ -202,8 +203,9 @@ protected BlockExtendedInputStream getOrOpenStream(int locationIndex) { StandaloneReplicationConfig.getInstance( HddsProtos.ReplicationFactor.ONE), blkInfo, pipeline, - blockInfo.getToken(), verifyChecksum, xceiverClientFactory, - ecPipelineRefreshFunction(locationIndex + 1, refreshFunction)); + blockInfo.getToken(), xceiverClientFactory, + ecPipelineRefreshFunction(locationIndex + 1, refreshFunction), + config); blockStreams[locationIndex] = stream; LOG.debug("{}: created stream [{}]: {}", this, locationIndex, stream); } @@ -237,6 +239,7 @@ protected Function ecPipelineRefreshFunction( HddsProtos.ReplicationFactor.ONE)) .setNodes(Collections.singletonList(curIndexNode)) .setId(PipelineID.randomId()) + .setReplicaIndexes(Collections.singletonMap(curIndexNode, replicaIndex)) .setState(Pipeline.PipelineState.CLOSED) .build(); blockLocationInfo.setPipeline(pipeline); diff --git a/hadoop-hdds/client/src/main/java/org/apache/hadoop/ozone/client/io/ECBlockInputStreamFactory.java b/hadoop-hdds/client/src/main/java/org/apache/hadoop/ozone/client/io/ECBlockInputStreamFactory.java index 0e2ef22c1e94..66e7a31337a6 100644 --- a/hadoop-hdds/client/src/main/java/org/apache/hadoop/ozone/client/io/ECBlockInputStreamFactory.java +++ b/hadoop-hdds/client/src/main/java/org/apache/hadoop/ozone/client/io/ECBlockInputStreamFactory.java @@ -20,6 +20,7 @@ import org.apache.hadoop.hdds.client.BlockID; import org.apache.hadoop.hdds.client.ReplicationConfig; import org.apache.hadoop.hdds.protocol.DatanodeDetails; +import org.apache.hadoop.hdds.scm.OzoneClientConfig; import org.apache.hadoop.hdds.scm.XceiverClientFactory; import org.apache.hadoop.hdds.scm.storage.BlockExtendedInputStream; import org.apache.hadoop.hdds.scm.storage.BlockLocationInfo; @@ -51,7 +52,8 @@ public interface ECBlockInputStreamFactory { */ BlockExtendedInputStream create(boolean missingLocations, List failedLocations, ReplicationConfig repConfig, - BlockLocationInfo blockInfo, boolean verifyChecksum, + BlockLocationInfo blockInfo, XceiverClientFactory xceiverFactory, - Function refreshFunction); + Function refreshFunction, + OzoneClientConfig config); } diff --git a/hadoop-hdds/client/src/main/java/org/apache/hadoop/ozone/client/io/ECBlockInputStreamFactoryImpl.java b/hadoop-hdds/client/src/main/java/org/apache/hadoop/ozone/client/io/ECBlockInputStreamFactoryImpl.java index 36b6539ea817..01d0b0a7b7e8 100644 --- a/hadoop-hdds/client/src/main/java/org/apache/hadoop/ozone/client/io/ECBlockInputStreamFactoryImpl.java +++ b/hadoop-hdds/client/src/main/java/org/apache/hadoop/ozone/client/io/ECBlockInputStreamFactoryImpl.java @@ -21,6 +21,7 @@ import org.apache.hadoop.hdds.client.ECReplicationConfig; import org.apache.hadoop.hdds.client.ReplicationConfig; import org.apache.hadoop.hdds.protocol.DatanodeDetails; +import org.apache.hadoop.hdds.scm.OzoneClientConfig; import org.apache.hadoop.hdds.scm.XceiverClientFactory; import org.apache.hadoop.hdds.scm.storage.BlockExtendedInputStream; import org.apache.hadoop.hdds.scm.storage.BlockLocationInfo; @@ -74,16 +75,17 @@ private ECBlockInputStreamFactoryImpl(BlockInputStreamFactory streamFactory, */ public BlockExtendedInputStream create(boolean missingLocations, List failedLocations, ReplicationConfig repConfig, - BlockLocationInfo blockInfo, boolean verifyChecksum, + BlockLocationInfo blockInfo, XceiverClientFactory xceiverFactory, - Function refreshFunction) { + Function refreshFunction, + OzoneClientConfig config) { if (missingLocations) { // We create the reconstruction reader ECBlockReconstructedStripeInputStream sis = new ECBlockReconstructedStripeInputStream( - (ECReplicationConfig)repConfig, blockInfo, verifyChecksum, + (ECReplicationConfig)repConfig, blockInfo, xceiverFactory, refreshFunction, inputStreamFactory, - byteBufferPool, ecReconstructExecutorSupplier.get()); + byteBufferPool, ecReconstructExecutorSupplier.get(), config); if (failedLocations != null) { sis.addFailedDatanodes(failedLocations); } @@ -92,7 +94,8 @@ public BlockExtendedInputStream create(boolean missingLocations, } else { // Otherwise create the more efficient non-reconstruction reader return new ECBlockInputStream((ECReplicationConfig)repConfig, blockInfo, - verifyChecksum, xceiverFactory, refreshFunction, inputStreamFactory); + xceiverFactory, refreshFunction, inputStreamFactory, + config); } } diff --git a/hadoop-hdds/client/src/main/java/org/apache/hadoop/ozone/client/io/ECBlockInputStreamProxy.java b/hadoop-hdds/client/src/main/java/org/apache/hadoop/ozone/client/io/ECBlockInputStreamProxy.java index 973561616f7b..68a0337cef1d 100644 --- a/hadoop-hdds/client/src/main/java/org/apache/hadoop/ozone/client/io/ECBlockInputStreamProxy.java +++ b/hadoop-hdds/client/src/main/java/org/apache/hadoop/ozone/client/io/ECBlockInputStreamProxy.java @@ -20,6 +20,7 @@ import org.apache.hadoop.hdds.client.BlockID; import org.apache.hadoop.hdds.client.ECReplicationConfig; import org.apache.hadoop.hdds.protocol.DatanodeDetails; +import org.apache.hadoop.hdds.scm.OzoneClientConfig; import org.apache.hadoop.hdds.scm.XceiverClientFactory; import org.apache.hadoop.hdds.scm.XceiverClientManager; import org.apache.hadoop.hdds.scm.pipeline.Pipeline; @@ -49,7 +50,6 @@ public class ECBlockInputStreamProxy extends BlockExtendedInputStream { LoggerFactory.getLogger(ECBlockInputStreamProxy.class); private final ECReplicationConfig repConfig; - private final boolean verifyChecksum; private final XceiverClientFactory xceiverClientFactory; private final Function refreshFunction; private final BlockLocationInfo blockInfo; @@ -59,6 +59,7 @@ public class ECBlockInputStreamProxy extends BlockExtendedInputStream { private boolean reconstructionReader = false; private List failedLocations = new ArrayList<>(); private boolean closed = false; + private OzoneClientConfig config; /** * Given the ECReplicationConfig and the block length, calculate how many @@ -97,16 +98,17 @@ public static int availableDataLocations(Pipeline pipeline, } public ECBlockInputStreamProxy(ECReplicationConfig repConfig, - BlockLocationInfo blockInfo, boolean verifyChecksum, + BlockLocationInfo blockInfo, XceiverClientFactory xceiverClientFactory, Function refreshFunction, - ECBlockInputStreamFactory streamFactory) { + ECBlockInputStreamFactory streamFactory, + OzoneClientConfig config) { this.repConfig = repConfig; - this.verifyChecksum = verifyChecksum; this.blockInfo = blockInfo; this.ecBlockInputStreamFactory = streamFactory; this.xceiverClientFactory = xceiverClientFactory; this.refreshFunction = refreshFunction; + this.config = config; setReaderType(); createBlockReader(); @@ -124,8 +126,8 @@ private void createBlockReader() { .incECReconstructionTotal(); } blockReader = ecBlockInputStreamFactory.create(reconstructionReader, - failedLocations, repConfig, blockInfo, verifyChecksum, - xceiverClientFactory, refreshFunction); + failedLocations, repConfig, blockInfo, + xceiverClientFactory, refreshFunction, config); } @Override diff --git a/hadoop-hdds/client/src/main/java/org/apache/hadoop/ozone/client/io/ECBlockReconstructedStripeInputStream.java b/hadoop-hdds/client/src/main/java/org/apache/hadoop/ozone/client/io/ECBlockReconstructedStripeInputStream.java index 142825cb1206..31f94e0acad6 100644 --- a/hadoop-hdds/client/src/main/java/org/apache/hadoop/ozone/client/io/ECBlockReconstructedStripeInputStream.java +++ b/hadoop-hdds/client/src/main/java/org/apache/hadoop/ozone/client/io/ECBlockReconstructedStripeInputStream.java @@ -23,6 +23,7 @@ import org.apache.hadoop.hdds.client.BlockID; import org.apache.hadoop.hdds.client.ECReplicationConfig; import org.apache.hadoop.hdds.protocol.DatanodeDetails; +import org.apache.hadoop.hdds.scm.OzoneClientConfig; import org.apache.hadoop.hdds.scm.XceiverClientFactory; import org.apache.hadoop.hdds.scm.storage.BlockExtendedInputStream; import org.apache.hadoop.hdds.scm.storage.BlockLocationInfo; @@ -152,14 +153,15 @@ public class ECBlockReconstructedStripeInputStream extends ECBlockInputStream { @SuppressWarnings("checkstyle:ParameterNumber") public ECBlockReconstructedStripeInputStream(ECReplicationConfig repConfig, - BlockLocationInfo blockInfo, boolean verifyChecksum, + BlockLocationInfo blockInfo, XceiverClientFactory xceiverClientFactory, Function refreshFunction, BlockInputStreamFactory streamFactory, ByteBufferPool byteBufferPool, - ExecutorService ecReconstructExecutor) { - super(repConfig, blockInfo, verifyChecksum, xceiverClientFactory, - refreshFunction, streamFactory); + ExecutorService ecReconstructExecutor, + OzoneClientConfig config) { + super(repConfig, blockInfo, xceiverClientFactory, + refreshFunction, streamFactory, config); this.byteBufferPool = byteBufferPool; this.executor = ecReconstructExecutor; diff --git a/hadoop-hdds/client/src/test/java/org/apache/hadoop/hdds/scm/storage/DummyBlockInputStream.java b/hadoop-hdds/client/src/test/java/org/apache/hadoop/hdds/scm/storage/DummyBlockInputStream.java index 3e7779f0d10a..46ce89da0f8a 100644 --- a/hadoop-hdds/client/src/test/java/org/apache/hadoop/hdds/scm/storage/DummyBlockInputStream.java +++ b/hadoop-hdds/client/src/test/java/org/apache/hadoop/hdds/scm/storage/DummyBlockInputStream.java @@ -24,6 +24,7 @@ import org.apache.hadoop.hdds.client.BlockID; import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.ChunkInfo; +import org.apache.hadoop.hdds.scm.OzoneClientConfig; import org.apache.hadoop.hdds.scm.XceiverClientFactory; import org.apache.hadoop.hdds.scm.pipeline.Pipeline; import org.apache.hadoop.hdds.security.token.OzoneBlockTokenIdentifier; @@ -44,13 +45,13 @@ class DummyBlockInputStream extends BlockInputStream { long blockLen, Pipeline pipeline, Token token, - boolean verifyChecksum, XceiverClientFactory xceiverClientManager, Function refreshFunction, List chunkList, - Map chunks) { - super(blockId, blockLen, pipeline, token, verifyChecksum, - xceiverClientManager, refreshFunction); + Map chunks, + OzoneClientConfig config) throws IOException { + super(blockId, blockLen, pipeline, token, + xceiverClientManager, refreshFunction, config); this.chunkDataMap = chunks; this.chunks = chunkList; diff --git a/hadoop-hdds/client/src/test/java/org/apache/hadoop/hdds/scm/storage/DummyBlockInputStreamWithRetry.java b/hadoop-hdds/client/src/test/java/org/apache/hadoop/hdds/scm/storage/DummyBlockInputStreamWithRetry.java index 24a35745144d..6ab31f8c3723 100644 --- a/hadoop-hdds/client/src/test/java/org/apache/hadoop/hdds/scm/storage/DummyBlockInputStreamWithRetry.java +++ b/hadoop-hdds/client/src/test/java/org/apache/hadoop/hdds/scm/storage/DummyBlockInputStreamWithRetry.java @@ -24,6 +24,7 @@ import org.apache.hadoop.hdds.client.BlockID; import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.ChunkInfo; +import org.apache.hadoop.hdds.scm.OzoneClientConfig; import org.apache.hadoop.hdds.scm.XceiverClientFactory; import org.apache.hadoop.hdds.scm.container.common.helpers.StorageContainerException; import org.apache.hadoop.hdds.scm.pipeline.MockPipeline; @@ -51,12 +52,12 @@ final class DummyBlockInputStreamWithRetry long blockLen, Pipeline pipeline, Token token, - boolean verifyChecksum, XceiverClientFactory xceiverClientManager, List chunkList, Map chunkMap, - AtomicBoolean isRerfreshed, IOException ioException) { - super(blockId, blockLen, pipeline, token, verifyChecksum, + AtomicBoolean isRerfreshed, IOException ioException, + OzoneClientConfig config) throws IOException { + super(blockId, blockLen, pipeline, token, xceiverClientManager, blockID -> { isRerfreshed.set(true); try { @@ -68,7 +69,7 @@ final class DummyBlockInputStreamWithRetry throw new RuntimeException(e); } - }, chunkList, chunkMap); + }, chunkList, chunkMap, config); this.ioException = ioException; } diff --git a/hadoop-hdds/client/src/test/java/org/apache/hadoop/hdds/scm/storage/TestBlockInputStream.java b/hadoop-hdds/client/src/test/java/org/apache/hadoop/hdds/scm/storage/TestBlockInputStream.java index 2e95de1ecad6..1094b942c5bb 100644 --- a/hadoop-hdds/client/src/test/java/org/apache/hadoop/hdds/scm/storage/TestBlockInputStream.java +++ b/hadoop-hdds/client/src/test/java/org/apache/hadoop/hdds/scm/storage/TestBlockInputStream.java @@ -22,8 +22,10 @@ import org.apache.hadoop.hdds.client.BlockID; import org.apache.hadoop.hdds.client.ContainerBlockID; import org.apache.hadoop.hdds.protocol.DatanodeDetails; +import org.apache.hadoop.hdds.conf.OzoneConfiguration; import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.ChecksumType; import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.ChunkInfo; +import org.apache.hadoop.hdds.scm.OzoneClientConfig; import org.apache.hadoop.hdds.scm.XceiverClientFactory; import org.apache.hadoop.hdds.scm.XceiverClientSpi; import org.apache.hadoop.hdds.scm.container.common.helpers.StorageContainerException; @@ -89,6 +91,8 @@ public class TestBlockInputStream { private Function refreshFunction; + private OzoneConfiguration conf = new OzoneConfiguration(); + @BeforeEach @SuppressWarnings("unchecked") public void setup() throws Exception { @@ -96,10 +100,12 @@ public void setup() throws Exception { BlockID blockID = new BlockID(new ContainerBlockID(1, 1)); checksum = new Checksum(ChecksumType.NONE, CHUNK_SIZE); createChunkList(5); + OzoneClientConfig clientConfig = conf.getObject(OzoneClientConfig.class); + clientConfig.setChecksumVerify(false); Pipeline pipeline = MockPipeline.createSingleNodePipeline(); blockStream = new DummyBlockInputStream(blockID, blockSize, pipeline, null, - false, null, refreshFunction, chunks, chunkDataMap); + null, refreshFunction, chunks, chunkDataMap, clientConfig); } /** @@ -264,11 +270,14 @@ public void testRefreshPipelineFunction() throws Exception { BlockID blockID = new BlockID(new ContainerBlockID(1, 1)); AtomicBoolean isRefreshed = new AtomicBoolean(); createChunkList(5); + OzoneClientConfig clientConfig = conf.getObject(OzoneClientConfig.class); + clientConfig.setChecksumVerify(false); try (BlockInputStream blockInputStreamWithRetry = new DummyBlockInputStreamWithRetry(blockID, blockSize, MockPipeline.createSingleNodePipeline(), null, - false, null, chunks, chunkDataMap, isRefreshed, null)) { + null, chunks, chunkDataMap, isRefreshed, null, + clientConfig)) { assertFalse(isRefreshed.get()); seekAndVerify(50); byte[] b = new byte[200]; @@ -351,9 +360,11 @@ private static ChunkInputStream throwingChunkInputStream(IOException ex, } private BlockInputStream createSubject(BlockID blockID, Pipeline pipeline, - ChunkInputStream stream) { - return new DummyBlockInputStream(blockID, blockSize, pipeline, null, false, - null, refreshFunction, chunks, null) { + ChunkInputStream stream) throws IOException { + OzoneClientConfig clientConfig = conf.getObject(OzoneClientConfig.class); + clientConfig.setChecksumVerify(false); + return new DummyBlockInputStream(blockID, blockSize, pipeline, null, + null, refreshFunction, chunks, null, clientConfig) { @Override protected ChunkInputStream createChunkInputStream(ChunkInfo chunkInfo) { return stream; @@ -411,8 +422,11 @@ public void testRefreshOnReadFailureAfterUnbuffer(IOException ex) .thenReturn(blockLocationInfo); when(blockLocationInfo.getPipeline()).thenReturn(newPipeline); + OzoneClientConfig clientConfig = conf.getObject(OzoneClientConfig.class); + clientConfig.setChecksumVerify(false); BlockInputStream subject = new BlockInputStream(blockID, blockSize, - pipeline, null, false, clientFactory, refreshFunction) { + pipeline, null, clientFactory, refreshFunction, + clientConfig) { @Override protected List getChunkInfoListUsingClient() { return chunks; diff --git a/hadoop-hdds/client/src/test/java/org/apache/hadoop/hdds/scm/storage/TestBlockOutputStreamCorrectness.java b/hadoop-hdds/client/src/test/java/org/apache/hadoop/hdds/scm/storage/TestBlockOutputStreamCorrectness.java index f52359ef6350..56388a8fc1e0 100644 --- a/hadoop-hdds/client/src/test/java/org/apache/hadoop/hdds/scm/storage/TestBlockOutputStreamCorrectness.java +++ b/hadoop-hdds/client/src/test/java/org/apache/hadoop/hdds/scm/storage/TestBlockOutputStreamCorrectness.java @@ -24,8 +24,12 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicInteger; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import org.apache.hadoop.hdds.client.BlockID; +import org.apache.hadoop.hdds.client.ECReplicationConfig; import org.apache.hadoop.hdds.protocol.DatanodeDetails; +import org.apache.hadoop.hdds.protocol.MockDatanodeDetails; import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.ChecksumType; import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.ContainerCommandRequestProto; import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.ContainerCommandResponseProto; @@ -43,12 +47,20 @@ import org.apache.hadoop.hdds.scm.pipeline.MockPipeline; import org.apache.hadoop.hdds.scm.pipeline.Pipeline; +import org.apache.hadoop.hdds.scm.pipeline.PipelineID; +import org.apache.hadoop.ozone.ClientVersion; +import org.apache.hadoop.ozone.container.common.helpers.BlockData; +import org.apache.hadoop.ozone.container.common.helpers.ChunkInfo; import org.apache.ratis.thirdparty.com.google.protobuf.ByteString; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import static java.util.concurrent.Executors.newFixedThreadPool; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; /** * UNIT test for BlockOutputStream. @@ -89,6 +101,66 @@ public void test() throws IOException { } } + /** + * Tests an EC offline reconstruction scenario in which none of the ChunkInfo in an EC stripe have stripeChecksum. + * Such ChunkInfo will exist for any EC data that was written in a version in which the ChunkInfo protobuf message did + * not have the stripeChecksum field. Here, we assert that executePutBlock during reconstruction does not throw an + * exception because of missing stripeChecksum. This essentially tests compatibility between an Ozone version that + * did not have stripeChecksum and a version that has stripeChecksum. + */ + @Test + public void testMissingStripeChecksumDoesNotMakeExecutePutBlockFailDuringECReconstruction() throws IOException { + // setup some parameters required for creating ECBlockOutputStream + OzoneClientConfig config = new OzoneClientConfig(); + ECReplicationConfig replicationConfig = new ECReplicationConfig(3, 2); + BlockID blockID = new BlockID(1, 1); + DatanodeDetails datanodeDetails = MockDatanodeDetails.randomDatanodeDetails(); + Pipeline pipeline = Pipeline.newBuilder() + .setId(PipelineID.valueOf(datanodeDetails.getUuid())) + .setReplicationConfig(replicationConfig) + .setNodes(ImmutableList.of(datanodeDetails)) + .setState(Pipeline.PipelineState.CLOSED) + // we'll executePutBlock for the parity index 5 because stripeChecksum is written to either the first or the + // parity indexes + .setReplicaIndexes(ImmutableMap.of(datanodeDetails, 5)).build(); + + BlockLocationInfo locationInfo = new BlockLocationInfo.Builder() + .setBlockID(blockID) + .setOffset(1) + .setLength(10) + .setPipeline(pipeline).build(); + + /* + The array of BlockData contains metadata about blocks and their chunks, and is read in executePutBlock. In + this test, we deliberately don't write stripeChecksum to any chunk. The expectation is that executePutBlock + should not throw an exception because of missing stripeChecksum. + */ + BlockData[] blockData = createBlockDataWithoutStripeChecksum(blockID, replicationConfig); + try (ECBlockOutputStream ecBlockOutputStream = createECBlockOutputStream(config, replicationConfig, blockID, + pipeline)) { + Assertions.assertDoesNotThrow(() -> ecBlockOutputStream.executePutBlock(true, true, locationInfo.getLength(), + blockData)); + } + } + + /** + * Creates a BlockData array with {@link ECReplicationConfig#getRequiredNodes()} number of elements. + */ + private BlockData[] createBlockDataWithoutStripeChecksum(BlockID blockID, ECReplicationConfig replicationConfig) { + int requiredNodes = replicationConfig.getRequiredNodes(); + BlockData[] blockDataArray = new BlockData[requiredNodes]; + + // add just one ChunkInfo to each BlockData. + for (int i = 0; i < requiredNodes; i++) { + BlockData data = new BlockData(blockID); + // create a ChunkInfo with no stripeChecksum + ChunkInfo chunkInfo = new ChunkInfo("abc", 0, 10); + data.addChunk(chunkInfo.getProtoBufMessage()); + blockDataArray[i] = data; + } + return blockDataArray; + } + private BlockOutputStream createBlockOutputStream(BufferPool bufferPool) throws IOException { @@ -120,6 +192,20 @@ private BlockOutputStream createBlockOutputStream(BufferPool bufferPool) () -> newFixedThreadPool(10)); } + private ECBlockOutputStream createECBlockOutputStream(OzoneClientConfig clientConfig, + ECReplicationConfig repConfig, BlockID blockID, Pipeline pipeline) throws IOException { + final XceiverClientManager xcm = mock(XceiverClientManager.class); + when(xcm.acquireClient(any())) + .thenReturn(new MockXceiverClientSpi(pipeline)); + + ContainerClientMetrics clientMetrics = ContainerClientMetrics.acquire(); + StreamBufferArgs streamBufferArgs = + StreamBufferArgs.getDefaultStreamBufferArgs(repConfig, clientConfig); + + return new ECBlockOutputStream(blockID, xcm, pipeline, BufferPool.empty(), clientConfig, null, + clientMetrics, streamBufferArgs, () -> newFixedThreadPool(2)); + } + /** * XCeiverClient which simulates responses. */ @@ -152,9 +238,12 @@ public Pipeline getPipeline() { } @Override - public XceiverClientReply sendCommandAsync( - ContainerCommandRequestProto request - ) { + public XceiverClientReply sendCommandAsync(ContainerCommandRequestProto request) { + + if (!request.hasVersion()) { + request = ContainerCommandRequestProto.newBuilder(request) + .setVersion(ClientVersion.CURRENT.toProtoValue()).build(); + } final ContainerCommandResponseProto.Builder builder = ContainerCommandResponseProto.newBuilder() .setResult(Result.SUCCESS) diff --git a/hadoop-hdds/client/src/test/java/org/apache/hadoop/ozone/client/io/ECStreamTestUtil.java b/hadoop-hdds/client/src/test/java/org/apache/hadoop/ozone/client/io/ECStreamTestUtil.java index 8db662cee070..dac71ba3db19 100644 --- a/hadoop-hdds/client/src/test/java/org/apache/hadoop/ozone/client/io/ECStreamTestUtil.java +++ b/hadoop-hdds/client/src/test/java/org/apache/hadoop/ozone/client/io/ECStreamTestUtil.java @@ -22,6 +22,7 @@ import org.apache.hadoop.hdds.client.ReplicationConfig; import org.apache.hadoop.hdds.protocol.DatanodeDetails; import org.apache.hadoop.hdds.protocol.MockDatanodeDetails; +import org.apache.hadoop.hdds.scm.OzoneClientConfig; import org.apache.hadoop.hdds.scm.XceiverClientFactory; import org.apache.hadoop.hdds.scm.pipeline.Pipeline; import org.apache.hadoop.hdds.scm.pipeline.PipelineID; @@ -258,9 +259,10 @@ public synchronized void setFailIndexes(Integer... fail) { public synchronized BlockExtendedInputStream create( ReplicationConfig repConfig, BlockLocationInfo blockInfo, Pipeline pipeline, - Token token, boolean verifyChecksum, + Token token, XceiverClientFactory xceiverFactory, - Function refreshFunction) { + Function refreshFunction, + OzoneClientConfig config) { int repInd = currentPipeline.getReplicaIndex(pipeline.getNodes().get(0)); TestBlockInputStream stream = new TestBlockInputStream( diff --git a/hadoop-hdds/client/src/test/java/org/apache/hadoop/ozone/client/io/TestBlockInputStreamFactoryImpl.java b/hadoop-hdds/client/src/test/java/org/apache/hadoop/ozone/client/io/TestBlockInputStreamFactoryImpl.java index abd69e5118c2..84386751bf83 100644 --- a/hadoop-hdds/client/src/test/java/org/apache/hadoop/ozone/client/io/TestBlockInputStreamFactoryImpl.java +++ b/hadoop-hdds/client/src/test/java/org/apache/hadoop/ozone/client/io/TestBlockInputStreamFactoryImpl.java @@ -21,9 +21,11 @@ import org.apache.hadoop.hdds.client.ECReplicationConfig; import org.apache.hadoop.hdds.client.RatisReplicationConfig; import org.apache.hadoop.hdds.client.ReplicationConfig; +import org.apache.hadoop.hdds.conf.OzoneConfiguration; import org.apache.hadoop.hdds.protocol.DatanodeDetails; import org.apache.hadoop.hdds.protocol.MockDatanodeDetails; import org.apache.hadoop.hdds.protocol.proto.HddsProtos; +import org.apache.hadoop.hdds.scm.OzoneClientConfig; import org.apache.hadoop.hdds.scm.pipeline.Pipeline; import org.apache.hadoop.hdds.scm.pipeline.PipelineID; import org.apache.hadoop.hdds.scm.storage.BlockExtendedInputStream; @@ -31,35 +33,46 @@ import org.apache.hadoop.hdds.scm.storage.BlockLocationInfo; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Assertions; +import org.mockito.Mockito; +import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; +import static org.mockito.ArgumentMatchers.any; + /** * Tests for BlockInputStreamFactoryImpl. */ public class TestBlockInputStreamFactoryImpl { + private OzoneConfiguration conf = new OzoneConfiguration(); + @Test - public void testNonECGivesBlockInputStream() { + public void testNonECGivesBlockInputStream() throws IOException { BlockInputStreamFactory factory = new BlockInputStreamFactoryImpl(); ReplicationConfig repConfig = RatisReplicationConfig.getInstance(HddsProtos.ReplicationFactor.THREE); BlockLocationInfo blockInfo = createKeyLocationInfo(repConfig, 3, 1024 * 1024 * 10); - + Pipeline pipeline = Mockito.spy(blockInfo.getPipeline()); + blockInfo.setPipeline(pipeline); + Mockito.when(pipeline.getReplicaIndex(any(DatanodeDetails.class))).thenReturn(1); + OzoneClientConfig clientConfig = conf.getObject(OzoneClientConfig.class); + clientConfig.setChecksumVerify(true); BlockExtendedInputStream stream = factory.create(repConfig, blockInfo, blockInfo.getPipeline(), - blockInfo.getToken(), true, null, null); + blockInfo.getToken(), null, null, + clientConfig); Assertions.assertTrue(stream instanceof BlockInputStream); Assertions.assertEquals(stream.getBlockID(), blockInfo.getBlockID()); Assertions.assertEquals(stream.getLength(), blockInfo.getLength()); } @Test - public void testECGivesECBlockInputStream() { + public void testECGivesECBlockInputStream() throws IOException { BlockInputStreamFactory factory = new BlockInputStreamFactoryImpl(); ReplicationConfig repConfig = new ECReplicationConfig(3, 2); @@ -67,9 +80,12 @@ public void testECGivesECBlockInputStream() { BlockLocationInfo blockInfo = createKeyLocationInfo(repConfig, 5, 1024 * 1024 * 10); + OzoneClientConfig clientConfig = conf.getObject(OzoneClientConfig.class); + clientConfig.setChecksumVerify(true); BlockExtendedInputStream stream = factory.create(repConfig, blockInfo, blockInfo.getPipeline(), - blockInfo.getToken(), true, null, null); + blockInfo.getToken(), null, null, + clientConfig); Assertions.assertTrue(stream instanceof ECBlockInputStreamProxy); Assertions.assertEquals(stream.getBlockID(), blockInfo.getBlockID()); Assertions.assertEquals(stream.getLength(), blockInfo.getLength()); diff --git a/hadoop-hdds/client/src/test/java/org/apache/hadoop/ozone/client/io/TestECBlockInputStream.java b/hadoop-hdds/client/src/test/java/org/apache/hadoop/ozone/client/io/TestECBlockInputStream.java index caa071b1b9ca..2ac575f6d45c 100644 --- a/hadoop-hdds/client/src/test/java/org/apache/hadoop/ozone/client/io/TestECBlockInputStream.java +++ b/hadoop-hdds/client/src/test/java/org/apache/hadoop/ozone/client/io/TestECBlockInputStream.java @@ -20,9 +20,11 @@ import org.apache.hadoop.hdds.client.BlockID; import org.apache.hadoop.hdds.client.ECReplicationConfig; import org.apache.hadoop.hdds.client.ReplicationConfig; +import org.apache.hadoop.hdds.conf.OzoneConfiguration; import org.apache.hadoop.hdds.protocol.DatanodeDetails; import org.apache.hadoop.hdds.protocol.MockDatanodeDetails; import org.apache.hadoop.hdds.protocol.proto.HddsProtos; +import org.apache.hadoop.hdds.scm.OzoneClientConfig; import org.apache.hadoop.hdds.scm.XceiverClientFactory; import org.apache.hadoop.hdds.scm.pipeline.Pipeline; import org.apache.hadoop.hdds.scm.pipeline.PipelineID; @@ -56,6 +58,7 @@ public class TestECBlockInputStream { private ECReplicationConfig repConfig; private TestBlockInputStreamFactory streamFactory; + private OzoneConfiguration conf = new OzoneConfiguration(); @BeforeEach public void setup() { @@ -69,15 +72,19 @@ public void testSufficientLocations() { // EC-3-2, 5MB block, so all 3 data locations are needed BlockLocationInfo keyInfo = ECStreamTestUtil .createKeyInfo(repConfig, 5, 5 * ONEMB); + OzoneClientConfig clientConfig = conf.getObject(OzoneClientConfig.class); + clientConfig.setChecksumVerify(true); try (ECBlockInputStream ecb = new ECBlockInputStream(repConfig, - keyInfo, true, null, null, new TestBlockInputStreamFactory())) { + keyInfo, null, null, new TestBlockInputStreamFactory(), + clientConfig)) { Assertions.assertTrue(ecb.hasSufficientLocations()); } // EC-3-2, very large block, so all 3 data locations are needed keyInfo = ECStreamTestUtil.createKeyInfo(repConfig, 5, 5000 * ONEMB); try (ECBlockInputStream ecb = new ECBlockInputStream(repConfig, - keyInfo, true, null, null, new TestBlockInputStreamFactory())) { + keyInfo, null, null, new TestBlockInputStreamFactory(), + clientConfig)) { Assertions.assertTrue(ecb.hasSufficientLocations()); } @@ -87,7 +94,8 @@ keyInfo, true, null, null, new TestBlockInputStreamFactory())) { dnMap.put(MockDatanodeDetails.randomDatanodeDetails(), 1); keyInfo = ECStreamTestUtil.createKeyInfo(repConfig, ONEMB - 1, dnMap); try (ECBlockInputStream ecb = new ECBlockInputStream(repConfig, - keyInfo, true, null, null, new TestBlockInputStreamFactory())) { + keyInfo, null, null, new TestBlockInputStreamFactory(), + clientConfig)) { Assertions.assertTrue(ecb.hasSufficientLocations()); } @@ -97,7 +105,8 @@ keyInfo, true, null, null, new TestBlockInputStreamFactory())) { dnMap.put(MockDatanodeDetails.randomDatanodeDetails(), 1); keyInfo = ECStreamTestUtil.createKeyInfo(repConfig, 5 * ONEMB, dnMap); try (ECBlockInputStream ecb = new ECBlockInputStream(repConfig, - keyInfo, true, null, null, new TestBlockInputStreamFactory())) { + keyInfo, null, null, new TestBlockInputStreamFactory(), + clientConfig)) { Assertions.assertFalse(ecb.hasSufficientLocations()); } @@ -109,7 +118,8 @@ keyInfo, true, null, null, new TestBlockInputStreamFactory())) { dnMap.put(MockDatanodeDetails.randomDatanodeDetails(), 5); keyInfo = ECStreamTestUtil.createKeyInfo(repConfig, 5 * ONEMB, dnMap); try (ECBlockInputStream ecb = new ECBlockInputStream(repConfig, - keyInfo, true, null, null, new TestBlockInputStreamFactory())) { + keyInfo, null, null, new TestBlockInputStreamFactory(), + clientConfig)) { Assertions.assertFalse(ecb.hasSufficientLocations()); } } @@ -121,8 +131,11 @@ public void testCorrectBlockSizePassedToBlockStreamLessThanCell() BlockLocationInfo keyInfo = ECStreamTestUtil.createKeyInfo(repConfig, 5, ONEMB - 100); + OzoneClientConfig clientConfig = conf.getObject(OzoneClientConfig.class); + clientConfig.setChecksumVerify(true); try (ECBlockInputStream ecb = new ECBlockInputStream(repConfig, - keyInfo, true, null, null, streamFactory)) { + keyInfo, null, null, streamFactory, + clientConfig)) { ecb.read(buf); // We expect only 1 block stream and it should have a length passed of // ONEMB - 100. @@ -138,8 +151,11 @@ public void testCorrectBlockSizePassedToBlockStreamTwoCells() BlockLocationInfo keyInfo = ECStreamTestUtil.createKeyInfo(repConfig, 5, ONEMB + 100); + OzoneClientConfig clientConfig = conf.getObject(OzoneClientConfig.class); + clientConfig.setChecksumVerify(true); try (ECBlockInputStream ecb = new ECBlockInputStream(repConfig, - keyInfo, true, null, null, streamFactory)) { + keyInfo, null, null, streamFactory, + clientConfig)) { ecb.read(buf); List streams = streamFactory.getBlockStreams(); Assertions.assertEquals(ONEMB, streams.get(0).getLength()); @@ -154,8 +170,11 @@ public void testCorrectBlockSizePassedToBlockStreamThreeCells() BlockLocationInfo keyInfo = ECStreamTestUtil.createKeyInfo(repConfig, 5, 2 * ONEMB + 100); + OzoneClientConfig clientConfig = conf.getObject(OzoneClientConfig.class); + clientConfig.setChecksumVerify(true); try (ECBlockInputStream ecb = new ECBlockInputStream(repConfig, - keyInfo, true, null, null, streamFactory)) { + keyInfo, null, null, streamFactory, + clientConfig)) { ecb.read(buf); List streams = streamFactory.getBlockStreams(); Assertions.assertEquals(ONEMB, streams.get(0).getLength()); @@ -171,8 +190,11 @@ public void testCorrectBlockSizePassedToBlockStreamThreeFullAndPartialStripe() BlockLocationInfo keyInfo = ECStreamTestUtil.createKeyInfo(repConfig, 5, 10 * ONEMB + 100); + OzoneClientConfig clientConfig = conf.getObject(OzoneClientConfig.class); + clientConfig.setChecksumVerify(true); try (ECBlockInputStream ecb = new ECBlockInputStream(repConfig, - keyInfo, true, null, null, streamFactory)) { + keyInfo, null, null, streamFactory, + clientConfig)) { ecb.read(buf); List streams = streamFactory.getBlockStreams(); Assertions.assertEquals(4 * ONEMB, streams.get(0).getLength()); @@ -188,8 +210,11 @@ public void testCorrectBlockSizePassedToBlockStreamSingleFullCell() BlockLocationInfo keyInfo = ECStreamTestUtil.createKeyInfo(repConfig, 5, ONEMB); + OzoneClientConfig clientConfig = conf.getObject(OzoneClientConfig.class); + clientConfig.setChecksumVerify(true); try (ECBlockInputStream ecb = new ECBlockInputStream(repConfig, - keyInfo, true, null, null, streamFactory)) { + keyInfo, null, null, streamFactory, + clientConfig)) { ecb.read(buf); List streams = streamFactory.getBlockStreams(); Assertions.assertEquals(ONEMB, streams.get(0).getLength()); @@ -203,8 +228,11 @@ public void testCorrectBlockSizePassedToBlockStreamSeveralFullCells() BlockLocationInfo keyInfo = ECStreamTestUtil.createKeyInfo(repConfig, 5, 9 * ONEMB); + OzoneClientConfig clientConfig = conf.getObject(OzoneClientConfig.class); + clientConfig.setChecksumVerify(true); try (ECBlockInputStream ecb = new ECBlockInputStream(repConfig, - keyInfo, true, null, null, streamFactory)) { + keyInfo, null, null, streamFactory, + clientConfig)) { ecb.read(buf); List streams = streamFactory.getBlockStreams(); Assertions.assertEquals(3 * ONEMB, streams.get(0).getLength()); @@ -217,8 +245,11 @@ public void testCorrectBlockSizePassedToBlockStreamSeveralFullCells() public void testSimpleRead() throws IOException { BlockLocationInfo keyInfo = ECStreamTestUtil.createKeyInfo(repConfig, 5, 5 * ONEMB); + OzoneClientConfig clientConfig = conf.getObject(OzoneClientConfig.class); + clientConfig.setChecksumVerify(true); try (ECBlockInputStream ecb = new ECBlockInputStream(repConfig, - keyInfo, true, null, null, streamFactory)) { + keyInfo, null, null, streamFactory, + clientConfig)) { ByteBuffer buf = ByteBuffer.allocate(100); @@ -240,8 +271,11 @@ public void testSimpleRead() throws IOException { public void testSimpleReadUnderOneChunk() throws IOException { BlockLocationInfo keyInfo = ECStreamTestUtil.createKeyInfo(repConfig, 1, ONEMB); + OzoneClientConfig clientConfig = conf.getObject(OzoneClientConfig.class); + clientConfig.setChecksumVerify(true); try (ECBlockInputStream ecb = new ECBlockInputStream(repConfig, - keyInfo, true, null, null, streamFactory)) { + keyInfo, null, null, streamFactory, + clientConfig)) { ByteBuffer buf = ByteBuffer.allocate(100); @@ -259,8 +293,11 @@ public void testSimpleReadUnderOneChunk() throws IOException { public void testReadPastEOF() throws IOException { BlockLocationInfo keyInfo = ECStreamTestUtil.createKeyInfo(repConfig, 5, 50); + OzoneClientConfig clientConfig = conf.getObject(OzoneClientConfig.class); + clientConfig.setChecksumVerify(true); try (ECBlockInputStream ecb = new ECBlockInputStream(repConfig, - keyInfo, true, null, null, streamFactory)) { + keyInfo, null, null, streamFactory, + clientConfig)) { ByteBuffer buf = ByteBuffer.allocate(100); @@ -278,8 +315,11 @@ public void testReadCrossingMultipleECChunkBounds() throws IOException { 100); BlockLocationInfo keyInfo = ECStreamTestUtil.createKeyInfo(repConfig, 5, 5 * ONEMB); + OzoneClientConfig clientConfig = conf.getObject(OzoneClientConfig.class); + clientConfig.setChecksumVerify(true); try (ECBlockInputStream ecb = new ECBlockInputStream(repConfig, - keyInfo, true, null, null, streamFactory)) { + keyInfo, null, null, streamFactory, + clientConfig)) { // EC Chunk size is 100 and 3-2. Create a byte buffer to read 3.5 chunks, // so 350 @@ -313,8 +353,11 @@ public void testSeekPastBlockLength() throws IOException { ONEMB); BlockLocationInfo keyInfo = ECStreamTestUtil.createKeyInfo(repConfig, 5, 100); + OzoneClientConfig clientConfig = conf.getObject(OzoneClientConfig.class); + clientConfig.setChecksumVerify(true); try (ECBlockInputStream ecb = new ECBlockInputStream(repConfig, - keyInfo, true, null, null, streamFactory)) { + keyInfo, null, null, streamFactory, + clientConfig)) { assertThrows(EOFException.class, () -> ecb.seek(1000)); } } @@ -325,8 +368,11 @@ public void testSeekToLength() throws IOException { ONEMB); BlockLocationInfo keyInfo = ECStreamTestUtil.createKeyInfo(repConfig, 5, 100); + OzoneClientConfig clientConfig = conf.getObject(OzoneClientConfig.class); + clientConfig.setChecksumVerify(true); try (ECBlockInputStream ecb = new ECBlockInputStream(repConfig, - keyInfo, true, null, null, streamFactory)) { + keyInfo, null, null, streamFactory, + clientConfig)) { // When seek more than the length, should throw EOFException. assertThrows(EOFException.class, () -> ecb.seek(101)); } @@ -338,8 +384,11 @@ public void testSeekToLengthZeroLengthBlock() throws IOException { ONEMB); BlockLocationInfo keyInfo = ECStreamTestUtil.createKeyInfo(repConfig, 5, 0); + OzoneClientConfig clientConfig = conf.getObject(OzoneClientConfig.class); + clientConfig.setChecksumVerify(true); try (ECBlockInputStream ecb = new ECBlockInputStream(repConfig, - keyInfo, true, null, null, streamFactory)) { + keyInfo, null, null, streamFactory, + clientConfig)) { ecb.seek(0); Assertions.assertEquals(0, ecb.getPos()); Assertions.assertEquals(0, ecb.getRemaining()); @@ -352,8 +401,11 @@ public void testSeekToValidPosition() throws IOException { ONEMB); BlockLocationInfo keyInfo = ECStreamTestUtil.createKeyInfo(repConfig, 5, 5 * ONEMB); + OzoneClientConfig clientConfig = conf.getObject(OzoneClientConfig.class); + clientConfig.setChecksumVerify(true); try (ECBlockInputStream ecb = new ECBlockInputStream(repConfig, - keyInfo, true, null, null, streamFactory)) { + keyInfo, null, null, streamFactory, + clientConfig)) { ecb.seek(ONEMB - 1); Assertions.assertEquals(ONEMB - 1, ecb.getPos()); Assertions.assertEquals(ONEMB * 4 + 1, ecb.getRemaining()); @@ -381,8 +433,11 @@ public void testErrorReadingBlockReportsBadLocation() throws IOException { ONEMB); BlockLocationInfo keyInfo = ECStreamTestUtil.createKeyInfo(repConfig, 5, 5 * ONEMB); + OzoneClientConfig clientConfig = conf.getObject(OzoneClientConfig.class); + clientConfig.setChecksumVerify(true); try (ECBlockInputStream ecb = new ECBlockInputStream(repConfig, - keyInfo, true, null, null, streamFactory)) { + keyInfo, null, null, streamFactory, + clientConfig)) { // Read a full stripe to ensure all streams are created in the stream // factory ByteBuffer buf = ByteBuffer.allocate(3 * ONEMB); @@ -412,8 +467,11 @@ public void testNoErrorIfSpareLocationToRead() throws IOException { BlockLocationInfo keyInfo = ECStreamTestUtil.createKeyInfo(repConfig, 8 * ONEMB, datanodes); + OzoneClientConfig clientConfig = conf.getObject(OzoneClientConfig.class); + clientConfig.setChecksumVerify(true); try (ECBlockInputStream ecb = new ECBlockInputStream(repConfig, - keyInfo, true, null, null, streamFactory)) { + keyInfo, null, null, streamFactory, + clientConfig)) { // Read a full stripe to ensure all streams are created in the stream // factory ByteBuffer buf = ByteBuffer.allocate(3 * ONEMB); @@ -476,8 +534,11 @@ public void testEcPipelineRefreshFunction() { return blockLocation; }; + OzoneClientConfig clientConfig = conf.getObject(OzoneClientConfig.class); + clientConfig.setChecksumVerify(true); try (ECBlockInputStream ecb = new ECBlockInputStream(repConfig, - keyInfo, true, null, null, streamFactory)) { + keyInfo, null, null, streamFactory, + clientConfig)) { Pipeline pipeline = ecb.ecPipelineRefreshFunction(3, refreshFunction) .apply(blockID) @@ -510,8 +571,9 @@ public synchronized List getBlockStreams() { public synchronized BlockExtendedInputStream create( ReplicationConfig repConfig, BlockLocationInfo blockInfo, Pipeline pipeline, Token token, - boolean verifyChecksum, XceiverClientFactory xceiverFactory, - Function refreshFunction) { + XceiverClientFactory xceiverFactory, + Function refreshFunction, + OzoneClientConfig config) { TestBlockInputStream stream = new TestBlockInputStream( blockInfo.getBlockID(), blockInfo.getLength(), (byte)blockStreams.size()); diff --git a/hadoop-hdds/client/src/test/java/org/apache/hadoop/ozone/client/io/TestECBlockInputStreamProxy.java b/hadoop-hdds/client/src/test/java/org/apache/hadoop/ozone/client/io/TestECBlockInputStreamProxy.java index 929fa13042e4..0afb05ff17a5 100644 --- a/hadoop-hdds/client/src/test/java/org/apache/hadoop/ozone/client/io/TestECBlockInputStreamProxy.java +++ b/hadoop-hdds/client/src/test/java/org/apache/hadoop/ozone/client/io/TestECBlockInputStreamProxy.java @@ -20,7 +20,9 @@ import org.apache.hadoop.hdds.client.BlockID; import org.apache.hadoop.hdds.client.ECReplicationConfig; import org.apache.hadoop.hdds.client.ReplicationConfig; +import org.apache.hadoop.hdds.conf.OzoneConfiguration; import org.apache.hadoop.hdds.protocol.DatanodeDetails; +import org.apache.hadoop.hdds.scm.OzoneClientConfig; import org.apache.hadoop.hdds.scm.XceiverClientFactory; import org.apache.hadoop.hdds.scm.storage.BlockExtendedInputStream; import org.apache.hadoop.hdds.scm.storage.BlockLocationInfo; @@ -56,6 +58,7 @@ public class TestECBlockInputStreamProxy { private long randomSeed; private ThreadLocalRandom random = ThreadLocalRandom.current(); private SplittableRandom dataGenerator; + private OzoneConfiguration conf = new OzoneConfiguration(); @BeforeEach public void setup() { @@ -346,8 +349,11 @@ private void resetAndAdvanceDataGenerator(long position) { private ECBlockInputStreamProxy createBISProxy(ECReplicationConfig rConfig, BlockLocationInfo blockInfo) { + OzoneClientConfig clientConfig = conf.getObject(OzoneClientConfig.class); + clientConfig.setChecksumVerify(true); return new ECBlockInputStreamProxy( - rConfig, blockInfo, true, null, null, streamFactory); + rConfig, blockInfo, null, null, streamFactory, + clientConfig); } private static class TestECBlockInputStreamFactory @@ -376,8 +382,9 @@ public List getFailedLocations() { public BlockExtendedInputStream create(boolean missingLocations, List failedDatanodes, ReplicationConfig repConfig, BlockLocationInfo blockInfo, - boolean verifyChecksum, XceiverClientFactory xceiverFactory, - Function refreshFunction) { + XceiverClientFactory xceiverFactory, + Function refreshFunction, + OzoneClientConfig config) { this.failedLocations = failedDatanodes; ByteBuffer wrappedBuffer = ByteBuffer.wrap(data.array(), 0, data.capacity()); diff --git a/hadoop-hdds/client/src/test/java/org/apache/hadoop/ozone/client/io/TestECBlockReconstructedInputStream.java b/hadoop-hdds/client/src/test/java/org/apache/hadoop/ozone/client/io/TestECBlockReconstructedInputStream.java index e39acaf9d234..78a3476d0964 100644 --- a/hadoop-hdds/client/src/test/java/org/apache/hadoop/ozone/client/io/TestECBlockReconstructedInputStream.java +++ b/hadoop-hdds/client/src/test/java/org/apache/hadoop/ozone/client/io/TestECBlockReconstructedInputStream.java @@ -19,7 +19,9 @@ import org.apache.hadoop.hdds.client.BlockID; import org.apache.hadoop.hdds.client.ECReplicationConfig; +import org.apache.hadoop.hdds.conf.OzoneConfiguration; import org.apache.hadoop.hdds.protocol.DatanodeDetails; +import org.apache.hadoop.hdds.scm.OzoneClientConfig; import org.apache.hadoop.hdds.scm.storage.BlockLocationInfo; import org.apache.hadoop.io.ByteBufferPool; import org.apache.hadoop.io.ElasticByteBufferPool; @@ -54,6 +56,7 @@ public class TestECBlockReconstructedInputStream { private ByteBufferPool bufferPool = new ElasticByteBufferPool(); private ExecutorService ecReconstructExecutor = Executors.newFixedThreadPool(3); + private OzoneConfiguration conf = new OzoneConfiguration(); @BeforeEach public void setup() throws IOException { @@ -74,8 +77,11 @@ private ECBlockReconstructedStripeInputStream createStripeInputStream( BlockLocationInfo keyInfo = ECStreamTestUtil.createKeyInfo(repConfig, blockLength, dnMap); streamFactory.setCurrentPipeline(keyInfo.getPipeline()); - return new ECBlockReconstructedStripeInputStream(repConfig, keyInfo, true, - null, null, streamFactory, bufferPool, ecReconstructExecutor); + OzoneClientConfig clientConfig = conf.getObject(OzoneClientConfig.class); + clientConfig.setChecksumVerify(true); + return new ECBlockReconstructedStripeInputStream(repConfig, keyInfo, + null, null, streamFactory, bufferPool, ecReconstructExecutor, + clientConfig); } @Test diff --git a/hadoop-hdds/client/src/test/java/org/apache/hadoop/ozone/client/io/TestECBlockReconstructedStripeInputStream.java b/hadoop-hdds/client/src/test/java/org/apache/hadoop/ozone/client/io/TestECBlockReconstructedStripeInputStream.java index 62d8c2d76023..83c864b6c660 100644 --- a/hadoop-hdds/client/src/test/java/org/apache/hadoop/ozone/client/io/TestECBlockReconstructedStripeInputStream.java +++ b/hadoop-hdds/client/src/test/java/org/apache/hadoop/ozone/client/io/TestECBlockReconstructedStripeInputStream.java @@ -19,8 +19,10 @@ import com.google.common.collect.ImmutableSet; import org.apache.hadoop.hdds.client.ECReplicationConfig; +import org.apache.hadoop.hdds.conf.OzoneConfiguration; import org.apache.hadoop.hdds.protocol.DatanodeDetails; import org.apache.hadoop.hdds.protocol.MockDatanodeDetails; +import org.apache.hadoop.hdds.scm.OzoneClientConfig; import org.apache.hadoop.hdds.scm.storage.BlockLocationInfo; import org.apache.hadoop.io.ByteBufferPool; import org.apache.hadoop.io.ElasticByteBufferPool; @@ -70,7 +72,8 @@ public class TestECBlockReconstructedStripeInputStream { private ByteBufferPool bufferPool = new ElasticByteBufferPool(); private ExecutorService ecReconstructExecutor = Executors.newFixedThreadPool(3); - + private OzoneConfiguration conf = new OzoneConfiguration(); + static List> recoveryCases() { // TODO better name List> params = new ArrayList<>(); params.add(emptySet()); // non-recovery @@ -821,8 +824,11 @@ public void testFailedLocationsAreNotRead() throws IOException { private ECBlockReconstructedStripeInputStream createInputStream( BlockLocationInfo keyInfo) { - return new ECBlockReconstructedStripeInputStream(repConfig, keyInfo, true, - null, null, streamFactory, bufferPool, ecReconstructExecutor); + OzoneClientConfig clientConfig = conf.getObject(OzoneClientConfig.class); + clientConfig.setChecksumVerify(true); + return new ECBlockReconstructedStripeInputStream(repConfig, keyInfo, + null, null, streamFactory, bufferPool, ecReconstructExecutor, + clientConfig); } private void addDataStreamsToFactory(ByteBuffer[] data, ByteBuffer[] parity) { diff --git a/hadoop-hdds/common/pom.xml b/hadoop-hdds/common/pom.xml index 17c9d541a5a5..2c0931732d2a 100644 --- a/hadoop-hdds/common/pom.xml +++ b/hadoop-hdds/common/pom.xml @@ -103,18 +103,8 @@ https://maven.apache.org/xsd/maven-4.0.0.xsd"> - ratis-server + ratis-server-api org.apache.ratis - - - org.slf4j - slf4j-reload4j - - - org.bouncycastle - bcprov-jdk18on - - ratis-metrics-dropwizard3 diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/client/BlockID.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/client/BlockID.java index 8540a0c5ab83..e3e3c3fa9ec1 100644 --- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/client/BlockID.java +++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/client/BlockID.java @@ -23,30 +23,41 @@ import java.util.Objects; /** - * BlockID of Ozone (containerID + localID + blockCommitSequenceId). + * BlockID of Ozone (containerID + localID + blockCommitSequenceId + replicaIndex). */ public class BlockID { private final ContainerBlockID containerBlockID; private long blockCommitSequenceId; + // null value when not set with private constructor.(This is to avoid confusion of replica index 0 & null value). + // This value would be only set when deserializing from ContainerProtos.DatanodeBlockID or copying from another + // BlockID object. + private final Integer replicaIndex; public BlockID(long containerID, long localID) { - this(containerID, localID, 0); + this(containerID, localID, 0, null); } - private BlockID(long containerID, long localID, long bcsID) { + private BlockID(long containerID, long localID, long bcsID, Integer repIndex) { containerBlockID = new ContainerBlockID(containerID, localID); blockCommitSequenceId = bcsID; + this.replicaIndex = repIndex; + } + + public BlockID(BlockID blockID) { + this(blockID.getContainerID(), blockID.getLocalID(), blockID.getBlockCommitSequenceId(), + blockID.getReplicaIndex()); } public BlockID(ContainerBlockID containerBlockID) { - this(containerBlockID, 0); + this(containerBlockID, 0, null); } - private BlockID(ContainerBlockID containerBlockID, long bcsId) { + private BlockID(ContainerBlockID containerBlockID, long bcsId, Integer repIndex) { this.containerBlockID = containerBlockID; blockCommitSequenceId = bcsId; + this.replicaIndex = repIndex; } public long getContainerID() { @@ -65,6 +76,11 @@ public void setBlockCommitSequenceId(long blockCommitSequenceId) { this.blockCommitSequenceId = blockCommitSequenceId; } + // Can return a null value in case it is not set. + public Integer getReplicaIndex() { + return replicaIndex; + } + public ContainerBlockID getContainerBlockID() { return containerBlockID; } @@ -79,21 +95,32 @@ public String toString() { public void appendTo(StringBuilder sb) { containerBlockID.appendTo(sb); sb.append(" bcsId: ").append(blockCommitSequenceId); + sb.append(" replicaIndex: ").append(replicaIndex); } @JsonIgnore public ContainerProtos.DatanodeBlockID getDatanodeBlockIDProtobuf() { + ContainerProtos.DatanodeBlockID.Builder blockID = getDatanodeBlockIDProtobufBuilder(); + if (replicaIndex != null) { + blockID.setReplicaIndex(replicaIndex); + } + return blockID.build(); + } + + @JsonIgnore + public ContainerProtos.DatanodeBlockID.Builder getDatanodeBlockIDProtobufBuilder() { return ContainerProtos.DatanodeBlockID.newBuilder(). setContainerID(containerBlockID.getContainerID()) .setLocalID(containerBlockID.getLocalID()) - .setBlockCommitSequenceId(blockCommitSequenceId).build(); + .setBlockCommitSequenceId(blockCommitSequenceId); } @JsonIgnore - public static BlockID getFromProtobuf( - ContainerProtos.DatanodeBlockID blockID) { + public static BlockID getFromProtobuf(ContainerProtos.DatanodeBlockID blockID) { return new BlockID(blockID.getContainerID(), - blockID.getLocalID(), blockID.getBlockCommitSequenceId()); + blockID.getLocalID(), + blockID.getBlockCommitSequenceId(), + blockID.hasReplicaIndex() ? blockID.getReplicaIndex() : null); } @JsonIgnore @@ -107,7 +134,7 @@ public HddsProtos.BlockID getProtobuf() { public static BlockID getFromProtobuf(HddsProtos.BlockID blockID) { return new BlockID( ContainerBlockID.getFromProtobuf(blockID.getContainerBlockID()), - blockID.getBlockCommitSequenceId()); + blockID.getBlockCommitSequenceId(), null); } @Override @@ -119,14 +146,14 @@ public boolean equals(Object o) { return false; } BlockID blockID = (BlockID) o; - return containerBlockID.equals(blockID.getContainerBlockID()) - && blockCommitSequenceId == blockID.getBlockCommitSequenceId(); + return this.getContainerBlockID().equals(blockID.getContainerBlockID()) + && this.getBlockCommitSequenceId() == blockID.getBlockCommitSequenceId() + && Objects.equals(this.getReplicaIndex(), blockID.getReplicaIndex()); } @Override public int hashCode() { - return Objects - .hash(containerBlockID.getContainerID(), containerBlockID.getLocalID(), - blockCommitSequenceId); + return Objects.hash(containerBlockID.getContainerID(), containerBlockID.getLocalID(), + blockCommitSequenceId, replicaIndex); } } diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/fs/CachingSpaceUsageSource.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/fs/CachingSpaceUsageSource.java index 7808dccaf5d0..1e070b48db20 100644 --- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/fs/CachingSpaceUsageSource.java +++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/fs/CachingSpaceUsageSource.java @@ -94,7 +94,19 @@ public void incrementUsedSpace(long usedSpace) { } public void decrementUsedSpace(long reclaimedSpace) { - cachedValue.addAndGet(-1 * reclaimedSpace); + cachedValue.updateAndGet(current -> { + long newValue = current - reclaimedSpace; + if (newValue < 0) { + if (current > 0) { + LOG.warn("Attempted to decrement used space to a negative value. " + + "Current: {}, Decrement: {}, Source: {}", + current, reclaimedSpace, source); + } + return 0; + } else { + return newValue; + } + }); } public void start() { diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/fs/SpaceUsageSource.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/fs/SpaceUsageSource.java index f3e54f375cb1..a367cfbdc061 100644 --- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/fs/SpaceUsageSource.java +++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/fs/SpaceUsageSource.java @@ -40,4 +40,47 @@ public interface SpaceUsageSource { long getCapacity(); long getAvailable(); + + default SpaceUsageSource snapshot() { + return new Fixed(getCapacity(), getAvailable(), getUsedSpace()); + } + + SpaceUsageSource UNKNOWN = new Fixed(0, 0, 0); + + /** + * A static source of space usage. Can be a point in time snapshot of a + * real volume usage, or can be used for testing. + */ + final class Fixed implements SpaceUsageSource { + + private final long capacity; + private final long available; + private final long used; + + public Fixed(long capacity, long available, long used) { + this.capacity = capacity; + this.available = Math.max(Math.min(available, capacity - used), 0); + this.used = used; + } + + @Override + public long getCapacity() { + return capacity; + } + + @Override + public long getAvailable() { + return available; + } + + @Override + public long getUsedSpace() { + return used; + } + + @Override + public SpaceUsageSource snapshot() { + return this; // immutable + } + } } diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/ratis/ContainerCommandRequestMessage.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/ratis/ContainerCommandRequestMessage.java index e1ebde25198c..7ae6e7859046 100644 --- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/ratis/ContainerCommandRequestMessage.java +++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/ratis/ContainerCommandRequestMessage.java @@ -24,6 +24,7 @@ import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.PutSmallFileRequestProto; import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.Type; import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.WriteChunkRequestProto; +import org.apache.hadoop.ozone.ClientVersion; import org.apache.hadoop.ozone.common.Checksum; import org.apache.ratis.protocol.Message; @@ -44,6 +45,9 @@ public static ContainerCommandRequestMessage toMessage( if (traceId != null) { b.setTraceID(traceId); } + if (!request.hasVersion()) { + b.setVersion(ClientVersion.CURRENT.toProtoValue()); + } ByteString data = ByteString.EMPTY; if (request.getCmdType() == Type.WriteChunk) { diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/ratis/RatisHelper.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/ratis/RatisHelper.java index b1d3e98e99a6..4d172ef6ab97 100644 --- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/ratis/RatisHelper.java +++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/ratis/RatisHelper.java @@ -59,6 +59,7 @@ import org.apache.ratis.protocol.RaftPeer; import org.apache.ratis.protocol.RaftPeerId; import org.apache.ratis.protocol.RoutingTable; +import org.apache.ratis.retry.RetryPolicies; import org.apache.ratis.retry.RetryPolicy; import org.apache.ratis.rpc.RpcType; import org.apache.ratis.rpc.SupportedRpcType; @@ -237,6 +238,12 @@ public static BiFunction newRaftClient( RatisHelper.createRetryPolicy(conf), tlsConfig, conf); } + public static BiFunction newRaftClientNoRetry( + ConfigurationSource conf) { + return (leader, tlsConfig) -> newRaftClient(getRpcType(conf), leader, + RetryPolicies.noRetry(), tlsConfig, conf); + } + public static RaftClient newRaftClient(RpcType rpcType, RaftPeer leader, RetryPolicy retryPolicy, GrpcTlsConfig tlsConfig, ConfigurationSource configuration) { diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/PipelineRequestInformation.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/PipelineRequestInformation.java index ac0cfbe57beb..4a4d91b2ff73 100644 --- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/PipelineRequestInformation.java +++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/PipelineRequestInformation.java @@ -22,7 +22,7 @@ * The information of the request of pipeline. */ public final class PipelineRequestInformation { - private long size; + private final long size; /** * Builder for PipelineRequestInformation. diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/ScmConfigKeys.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/ScmConfigKeys.java index 7e01afd55989..007dc3dfaef8 100644 --- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/ScmConfigKeys.java +++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/ScmConfigKeys.java @@ -223,7 +223,7 @@ public final class ScmConfigKeys { "hdds.datanode.dir.du.reserved"; public static final String HDDS_DATANODE_DIR_DU_RESERVED_PERCENT = "hdds.datanode.dir.du.reserved.percent"; - public static final float HDDS_DATANODE_DIR_DU_RESERVED_PERCENT_DEFAULT = 0; + public static final float HDDS_DATANODE_DIR_DU_RESERVED_PERCENT_DEFAULT = 0.0001f; public static final String HDDS_REST_CSRF_ENABLED_KEY = "hdds.rest.rest-csrf.enabled"; public static final boolean HDDS_REST_CSRF_ENABLED_DEFAULT = false; @@ -233,7 +233,6 @@ public final class ScmConfigKeys { public static final int HDDS_REST_NETTY_LOW_WATERMARK_DEFAULT = 32768; public static final String HDDS_REST_NETTY_LOW_WATERMARK = "hdds.rest.netty.low.watermark"; - public static final String OZONE_SCM_HANDLER_COUNT_KEY = "ozone.scm.handler.count.key"; public static final int OZONE_SCM_HANDLER_COUNT_DEFAULT = 100; diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/pipeline/Pipeline.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/pipeline/Pipeline.java index 2a117de35b46..e25b37435c99 100644 --- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/pipeline/Pipeline.java +++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/pipeline/Pipeline.java @@ -32,6 +32,8 @@ import java.util.Optional; import java.util.Set; import java.util.UUID; +import java.util.function.Function; +import java.util.stream.Collectors; import com.fasterxml.jackson.annotation.JsonIgnore; import org.apache.commons.lang3.StringUtils; @@ -238,6 +240,14 @@ public int getReplicaIndex(DatanodeDetails dn) { return replicaIndexes.getOrDefault(dn, 0); } + /** + * Get the replicaIndex Map. + * @return + */ + public Map getReplicaIndexes() { + return this.getNodes().stream().collect(Collectors.toMap(Function.identity(), this::getReplicaIndex)); + } + /** * Returns the leader if found else defaults to closest node. * @@ -507,7 +517,10 @@ public String toString() { new StringBuilder(getClass().getSimpleName()).append("["); b.append(" Id: ").append(id.getId()); b.append(", Nodes: "); - nodeStatus.keySet().forEach(b::append); + for (DatanodeDetails datanodeDetails : nodeStatus.keySet()) { + b.append(datanodeDetails); + b.append(" ReplicaIndex: ").append(this.getReplicaIndex(datanodeDetails)); + } b.append(", ReplicationConfig: ").append(replicationConfig); b.append(", State:").append(getPipelineState()); b.append(", leaderId:").append(leaderId != null ? leaderId.toString() : ""); diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/storage/ContainerProtocolCalls.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/storage/ContainerProtocolCalls.java index 638282b30c82..d83922ec130b 100644 --- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/storage/ContainerProtocolCalls.java +++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/storage/ContainerProtocolCalls.java @@ -161,49 +161,52 @@ static T tryEachDatanode(Pipeline pipeline, * * @param xceiverClient client to perform call * @param validators functions to validate the response - * @param datanodeBlockID blockID to identify container + * @param blockID blockID to identify container * @param token a token for this block (may be null) * @return container protocol get block response * @throws IOException if there is an I/O error while performing the call */ public static GetBlockResponseProto getBlock(XceiverClientSpi xceiverClient, - List validators, - DatanodeBlockID datanodeBlockID, - Token token) throws IOException { - GetBlockRequestProto.Builder readBlockRequest = GetBlockRequestProto - .newBuilder() - .setBlockID(datanodeBlockID); + List validators, BlockID blockID, Token token, + Map replicaIndexes) throws IOException { ContainerCommandRequestProto.Builder builder = ContainerCommandRequestProto .newBuilder() .setCmdType(Type.GetBlock) - .setContainerID(datanodeBlockID.getContainerID()) - .setGetBlock(readBlockRequest); + .setContainerID(blockID.getContainerID()); if (token != null) { builder.setEncodedToken(token.encodeToUrlString()); } return tryEachDatanode(xceiverClient.getPipeline(), - d -> getBlock(xceiverClient, validators, builder, d), - d -> toErrorMessage(datanodeBlockID, d)); + d -> getBlock(xceiverClient, validators, builder, blockID, d, replicaIndexes), + d -> toErrorMessage(blockID, d)); } - static String toErrorMessage(DatanodeBlockID blockId, DatanodeDetails d) { + static String toErrorMessage(BlockID blockId, DatanodeDetails d) { return String.format("Failed to get block #%s in container #%s from %s", blockId.getLocalID(), blockId.getContainerID(), d); } public static GetBlockResponseProto getBlock(XceiverClientSpi xceiverClient, - DatanodeBlockID datanodeBlockID, - Token token) throws IOException { - return getBlock(xceiverClient, getValidatorList(), datanodeBlockID, token); + BlockID datanodeBlockID, + Token token, Map replicaIndexes) throws IOException { + return getBlock(xceiverClient, getValidatorList(), datanodeBlockID, token, replicaIndexes); } private static GetBlockResponseProto getBlock(XceiverClientSpi xceiverClient, List validators, - ContainerCommandRequestProto.Builder builder, - DatanodeDetails datanode) throws IOException { + ContainerCommandRequestProto.Builder builder, BlockID blockID, + DatanodeDetails datanode, Map replicaIndexes) throws IOException { + final DatanodeBlockID.Builder datanodeBlockID = blockID.getDatanodeBlockIDProtobufBuilder(); + int replicaIndex = replicaIndexes.getOrDefault(datanode, 0); + if (replicaIndex > 0) { + datanodeBlockID.setReplicaIndex(replicaIndex); + } + final GetBlockRequestProto.Builder readBlockRequest = GetBlockRequestProto.newBuilder() + .setBlockID(datanodeBlockID.build()); final ContainerCommandRequestProto request = builder - .setDatanodeUuid(datanode.getUuidString()).build(); + .setDatanodeUuid(datanode.getUuidString()) + .setGetBlock(readBlockRequest).build(); ContainerCommandResponseProto response = xceiverClient.sendCommand(request, validators); return response.getGetBlock(); @@ -323,12 +326,12 @@ public static ContainerCommandRequestProto getPutBlockRequest( * @throws IOException if there is an I/O error while performing the call */ public static ContainerProtos.ReadChunkResponseProto readChunk( - XceiverClientSpi xceiverClient, ChunkInfo chunk, BlockID blockID, + XceiverClientSpi xceiverClient, ChunkInfo chunk, DatanodeBlockID blockID, List validators, Token token) throws IOException { ReadChunkRequestProto.Builder readChunkRequest = ReadChunkRequestProto.newBuilder() - .setBlockID(blockID.getDatanodeBlockIDProtobuf()) + .setBlockID(blockID) .setChunkData(chunk) .setReadChunkVersion(ContainerProtos.ReadChunkVersion.V1); ContainerCommandRequestProto.Builder builder = @@ -346,7 +349,7 @@ public static ContainerProtos.ReadChunkResponseProto readChunk( } private static ContainerProtos.ReadChunkResponseProto readChunk( - XceiverClientSpi xceiverClient, ChunkInfo chunk, BlockID blockID, + XceiverClientSpi xceiverClient, ChunkInfo chunk, DatanodeBlockID blockID, List validators, ContainerCommandRequestProto.Builder builder, DatanodeDetails d) throws IOException { @@ -363,7 +366,7 @@ private static ContainerProtos.ReadChunkResponseProto readChunk( return response; } - static String toErrorMessage(ChunkInfo chunk, BlockID blockId, + static String toErrorMessage(ChunkInfo chunk, DatanodeBlockID blockId, DatanodeDetails d) { return String.format("Failed to read chunk %s (len=%s) %s from %s", chunk.getChunkName(), chunk.getLen(), blockId, d); diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/ClientVersion.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/ClientVersion.java index cc6695dc7d68..f3bd1a96b662 100644 --- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/ClientVersion.java +++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/ClientVersion.java @@ -42,6 +42,10 @@ public enum ClientVersion implements ComponentVersion { "This client version has support for Object Store and File " + "System Optimized Bucket Layouts."), + EC_REPLICA_INDEX_REQUIRED_IN_BLOCK_REQUEST(4, + "This client version enforces replica index is set for fixing read corruption that could occur when " + + "replicaIndex parameter is not validated before EC block reads."), + FUTURE_VERSION(-1, "Used internally when the server side is older and an" + " unknown client version has arrived from the client."); diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConfigKeys.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConfigKeys.java index f124e24141f5..21c89cc3c8d4 100644 --- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConfigKeys.java +++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConfigKeys.java @@ -668,6 +668,14 @@ public final class OzoneConfigKeys { public static final String OZONE_SCM_CLOSE_CONTAINER_WAIT_DURATION = "ozone.scm.close.container.wait.duration"; + public static final String HDDS_SCM_CLIENT_RPC_TIME_OUT = + "hdds.scmclient.rpc.timeout"; + public static final String HDDS_SCM_CLIENT_MAX_RETRY_TIMEOUT = + "hdds.scmclient.max.retry.timeout"; + public static final String HDDS_SCM_CLIENT_FAILOVER_MAX_RETRY = + "hdds.scmclient.failover.max.retry"; + + /** * There is no need to instantiate this class. */ diff --git a/hadoop-hdds/common/src/main/resources/ozone-default.xml b/hadoop-hdds/common/src/main/resources/ozone-default.xml index c70a9630f039..cd3a01aabc23 100644 --- a/hadoop-hdds/common/src/main/resources/ozone-default.xml +++ b/hadoop-hdds/common/src/main/resources/ozone-default.xml @@ -3118,6 +3118,32 @@ SCM snapshot. + + ozone.recon.scmclient.rpc.timeout + 1m + OZONE, RECON, SCM + + RpcClient timeout on waiting for the response from SCM when Recon connects to SCM. + + + + ozone.recon.scmclient.max.retry.timeout + 6s + OZONE, RECON, SCM + + Max retry timeout for SCM Client when Recon connects to SCM. This config is used to + dynamically compute the max retry count for SCM Client when failover happens. Check the + SCMClientConfig class getRetryCount method. + + + + ozone.recon.scmclient.failover.max.retry + 3 + OZONE, RECON, SCM + + Max retry count for SCM Client when failover happens. + + ozone.recon.om.socket.timeout 5s @@ -4179,7 +4205,7 @@ - ozone.om.snapshot.diff.cleanup.service.run.internal + ozone.om.snapshot.diff.cleanup.service.run.interval 1m OZONE, OM @@ -4198,6 +4224,16 @@ + + ozone.om.snapshot.cache.cleanup.service.run.interval + 1m + OZONE, OM + + Interval at which snapshot cache clean up will run. + Uses millisecond by default when no time unit is specified. + + + ozone.om.snapshot.sst_dumptool.pool.size 1 diff --git a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/fs/MockSpaceUsageSource.java b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/fs/MockSpaceUsageSource.java index 4055f080b995..286afda91767 100644 --- a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/fs/MockSpaceUsageSource.java +++ b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/fs/MockSpaceUsageSource.java @@ -17,6 +17,8 @@ */ package org.apache.hadoop.hdds.fs; +import java.util.concurrent.atomic.AtomicLong; + /** * {@link SpaceUsageSource} implementations for testing. */ @@ -36,35 +38,27 @@ public static SpaceUsageSource fixed(long capacity, long available) { public static SpaceUsageSource fixed(long capacity, long available, long used) { - return new Fixed(capacity, available, used); + return new SpaceUsageSource.Fixed(capacity, available, used); } - private static final class Fixed implements SpaceUsageSource { - - private final long capacity; - private final long available; - private final long used; - - Fixed(long capacity, long available, long used) { - this.capacity = capacity; - this.available = available; - this.used = used; - } - - @Override - public long getCapacity() { - return capacity; - } + /** @return {@code SpaceUsageSource} with fixed capacity and dynamic usage */ + public static SpaceUsageSource of(long capacity, AtomicLong used) { + return new SpaceUsageSource() { + @Override + public long getUsedSpace() { + return used.get(); + } - @Override - public long getAvailable() { - return available; - } + @Override + public long getCapacity() { + return capacity; + } - @Override - public long getUsedSpace() { - return used; - } + @Override + public long getAvailable() { + return getCapacity() - getUsedSpace(); + } + }; } private MockSpaceUsageSource() { diff --git a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/fs/TestCachingSpaceUsageSource.java b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/fs/TestCachingSpaceUsageSource.java index 9c701ca1fc77..6034ae7b1c1e 100644 --- a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/fs/TestCachingSpaceUsageSource.java +++ b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/fs/TestCachingSpaceUsageSource.java @@ -142,6 +142,20 @@ public void savesValueOnShutdown() { verify(executor).shutdown(); } + @Test + public void testDecrementDoesNotGoNegative() { + SpaceUsageCheckParams params = paramsBuilder(new AtomicLong(50)) + .withRefresh(Duration.ZERO) + .build(); + CachingSpaceUsageSource subject = new CachingSpaceUsageSource(params); + + // Try to decrement more than the current value + subject.decrementUsedSpace(100); + + // Check that the value has been set to 0 + assertEquals(0, subject.getUsedSpace()); + } + private static long missingInitialValue() { return 0L; } diff --git a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/ratis/TestContainerCommandRequestMessage.java b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/ratis/TestContainerCommandRequestMessage.java index 75e19f5d458c..673aae7c557f 100644 --- a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/ratis/TestContainerCommandRequestMessage.java +++ b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/ratis/TestContainerCommandRequestMessage.java @@ -31,6 +31,7 @@ import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.PutSmallFileRequestProto; import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.Type; import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.WriteChunkRequestProto; +import org.apache.hadoop.ozone.ClientVersion; import org.apache.hadoop.ozone.common.Checksum; import org.apache.hadoop.ozone.common.ChecksumData; import org.apache.hadoop.ozone.common.OzoneChecksumException; @@ -90,6 +91,7 @@ static ContainerCommandRequestProto newPutSmallFile( .setContainerID(blockID.getContainerID()) .setDatanodeUuid(UUID.randomUUID().toString()) .setPutSmallFile(putSmallFileRequest) + .setVersion(ClientVersion.CURRENT.toProtoValue()) .build(); } @@ -112,6 +114,7 @@ static ContainerCommandRequestProto newWriteChunk( .setContainerID(blockID.getContainerID()) .setDatanodeUuid(UUID.randomUUID().toString()) .setWriteChunk(writeChunkRequest) + .setVersion(ClientVersion.CURRENT.toProtoValue()) .build(); } diff --git a/hadoop-hdds/common/src/test/java/org/apache/hadoop/ozone/container/ContainerTestHelper.java b/hadoop-hdds/common/src/test/java/org/apache/hadoop/ozone/container/ContainerTestHelper.java index e7474a0b8d46..9377f4f92729 100644 --- a/hadoop-hdds/common/src/test/java/org/apache/hadoop/ozone/container/ContainerTestHelper.java +++ b/hadoop-hdds/common/src/test/java/org/apache/hadoop/ozone/container/ContainerTestHelper.java @@ -38,6 +38,7 @@ import org.apache.hadoop.hdds.scm.pipeline.MockPipeline; import org.apache.hadoop.hdds.scm.pipeline.Pipeline; import org.apache.hadoop.hdds.utils.UniqueId; +import org.apache.hadoop.ozone.ClientVersion; import org.apache.hadoop.ozone.common.Checksum; import org.apache.hadoop.ozone.common.ChunkBuffer; import org.apache.hadoop.ozone.common.OzoneChecksumException; @@ -544,7 +545,16 @@ private static void sleep(long milliseconds) { } public static BlockID getTestBlockID(long containerID) { - return new BlockID(containerID, UniqueId.next()); + return getTestBlockID(containerID, null); + } + + public static BlockID getTestBlockID(long containerID, Integer replicaIndex) { + DatanodeBlockID.Builder datanodeBlockID = DatanodeBlockID.newBuilder().setContainerID(containerID) + .setLocalID(UniqueId.next()); + if (replicaIndex != null) { + datanodeBlockID.setReplicaIndex(replicaIndex); + } + return BlockID.getFromProtobuf(datanodeBlockID.build()); } public static long getTestContainerID() { @@ -567,6 +577,11 @@ public static byte[] generateData(int length, boolean random) { return data; } + public static ContainerCommandRequestProto getDummyCommandRequestProto( + ContainerProtos.Type cmdType) { + return getDummyCommandRequestProto(ClientVersion.CURRENT, cmdType, 0); + } + /** * Construct fake protobuf messages for various types of requests. * This is tedious, however necessary to test. Protobuf classes are final @@ -576,16 +591,17 @@ public static byte[] generateData(int length, boolean random) { * @return */ public static ContainerCommandRequestProto getDummyCommandRequestProto( - ContainerProtos.Type cmdType) { + ClientVersion clientVersion, ContainerProtos.Type cmdType, int replicaIndex) { final Builder builder = ContainerCommandRequestProto.newBuilder() + .setVersion(clientVersion.toProtoValue()) .setCmdType(cmdType) .setContainerID(DUMMY_CONTAINER_ID) .setDatanodeUuid(DATANODE_UUID); final DatanodeBlockID fakeBlockId = DatanodeBlockID.newBuilder() - .setContainerID(DUMMY_CONTAINER_ID).setLocalID(1) + .setContainerID(DUMMY_CONTAINER_ID).setLocalID(1).setReplicaIndex(replicaIndex) .setBlockCommitSequenceId(101).build(); final ContainerProtos.ChunkInfo fakeChunkInfo = diff --git a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/impl/HddsDispatcher.java b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/impl/HddsDispatcher.java index 1b0ef29c770c..a0f2735c6008 100644 --- a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/impl/HddsDispatcher.java +++ b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/impl/HddsDispatcher.java @@ -25,6 +25,7 @@ import org.apache.hadoop.hdds.HddsUtils; import org.apache.hadoop.hdds.client.BlockID; import org.apache.hadoop.hdds.conf.ConfigurationSource; +import org.apache.hadoop.hdds.fs.SpaceUsageSource; import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos; import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.ContainerCommandRequestProto; import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.ContainerCommandResponseProto; @@ -78,7 +79,6 @@ import static org.apache.hadoop.hdds.scm.protocolPB.ContainerCommandResponseBuilders.malformedRequest; import static org.apache.hadoop.hdds.scm.protocolPB.ContainerCommandResponseBuilders.unsupportedRequest; import static org.apache.hadoop.ozone.container.common.interfaces.Container.ScanResult; -import static org.apache.hadoop.ozone.container.common.volume.VolumeUsage.PrecomputedVolumeSpace; /** * Ozone Container dispatcher takes a call from the netty server and routes it @@ -585,12 +585,12 @@ private boolean isVolumeFull(Container container) { .orElse(Boolean.FALSE); if (isOpen) { HddsVolume volume = container.getContainerData().getVolume(); - PrecomputedVolumeSpace precomputedVolumeSpace = - volume.getPrecomputedVolumeSpace(); + SpaceUsageSource precomputedVolumeSpace = + volume.getCurrentUsage(); long volumeCapacity = precomputedVolumeSpace.getCapacity(); long volumeFreeSpaceToSpare = VolumeUsage.getMinVolumeFreeSpace(conf, volumeCapacity); - long volumeFree = volume.getAvailable(precomputedVolumeSpace); + long volumeFree = precomputedVolumeSpace.getAvailable(); long volumeCommitted = volume.getCommittedBytes(); long volumeAvailable = volumeFree - volumeCommitted; return (volumeAvailable <= volumeFreeSpaceToSpare); diff --git a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/impl/StorageLocationReport.java b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/impl/StorageLocationReport.java index 0222050da5e5..f31d45a7782b 100644 --- a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/impl/StorageLocationReport.java +++ b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/impl/StorageLocationReport.java @@ -19,6 +19,7 @@ package org.apache.hadoop.ozone.container.common.impl; import org.apache.hadoop.fs.StorageType; +import org.apache.hadoop.hdds.conf.ConfigurationSource; import org.apache.hadoop.hdds.protocol.proto. StorageContainerDatanodeProtocolProtos.MetadataStorageReportProto; import org.apache.hadoop.hdds.protocol.proto. @@ -27,6 +28,7 @@ StorageContainerDatanodeProtocolProtos.StorageTypeProto; import org.apache.hadoop.ozone.container.common.interfaces .StorageLocationReportMXBean; +import org.apache.hadoop.ozone.container.common.volume.VolumeUsage; import java.io.IOException; @@ -42,17 +44,22 @@ public final class StorageLocationReport implements private final long capacity; private final long scmUsed; private final long remaining; + private final long committed; + private final long freeSpaceToSpare; private final StorageType storageType; private final String storageLocation; + @SuppressWarnings("checkstyle:parameternumber") private StorageLocationReport(String id, boolean failed, long capacity, - long scmUsed, long remaining, StorageType storageType, - String storageLocation) { + long scmUsed, long remaining, long committed, long freeSpaceToSpare, + StorageType storageType, String storageLocation) { this.id = id; this.failed = failed; this.capacity = capacity; this.scmUsed = scmUsed; this.remaining = remaining; + this.committed = committed; + this.freeSpaceToSpare = freeSpaceToSpare; this.storageType = storageType; this.storageLocation = storageLocation; } @@ -82,6 +89,16 @@ public long getRemaining() { return remaining; } + @Override + public long getCommitted() { + return committed; + } + + @Override + public long getFreeSpaceToSpare() { + return freeSpaceToSpare; + } + @Override public String getStorageLocation() { return storageLocation; @@ -157,14 +174,22 @@ private static StorageType getStorageType(StorageTypeProto proto) throws * @throws IOException In case, the storage type specified is invalid. */ public StorageReportProto getProtoBufMessage() throws IOException { + return getProtoBufMessage(null); + } + + public StorageReportProto getProtoBufMessage(ConfigurationSource conf) + throws IOException { StorageReportProto.Builder srb = StorageReportProto.newBuilder(); return srb.setStorageUuid(getId()) .setCapacity(getCapacity()) .setScmUsed(getScmUsed()) .setRemaining(getRemaining()) + .setCommitted(getCommitted()) .setStorageType(getStorageTypeProto()) .setStorageLocation(getStorageLocation()) .setFailed(isFailed()) + .setFreeSpaceToSpare(conf != null ? + VolumeUsage.getMinVolumeFreeSpace(conf, getCapacity()) : 0) .build(); } @@ -266,6 +291,8 @@ public static class Builder { private long capacity; private long scmUsed; private long remaining; + private long committed; + private long freeSpaceToSpare; private StorageType storageType; private String storageLocation; @@ -334,6 +361,29 @@ public Builder setStorageType(StorageType storageTypeValue) { return this; } + /** + * Sets the committed bytes count. + * (bytes for previously created containers) + * @param committed previously created containers size + * @return StorageLocationReport.Builder + */ + public Builder setCommitted(long committed) { + this.committed = committed; + return this; + } + + /** + * Sets the free space available to spare. + * (depends on datanode volume config, + * consider 'hdds.datanode.volume.min.*' configuration properties) + * @param freeSpaceToSpare the size of free volume space available to spare + * @return StorageLocationReport.Builder + */ + public Builder setFreeSpaceToSpare(long freeSpaceToSpare) { + this.freeSpaceToSpare = freeSpaceToSpare; + return this; + } + /** * Sets the storageLocation. * @@ -352,7 +402,7 @@ public Builder setStorageLocation(String storageLocationValue) { */ public StorageLocationReport build() { return new StorageLocationReport(id, failed, capacity, scmUsed, - remaining, storageType, storageLocation); + remaining, committed, freeSpaceToSpare, storageType, storageLocation); } } diff --git a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/interfaces/StorageLocationReportMXBean.java b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/interfaces/StorageLocationReportMXBean.java index fd063678137d..74c4336bc652 100644 --- a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/interfaces/StorageLocationReportMXBean.java +++ b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/interfaces/StorageLocationReportMXBean.java @@ -33,6 +33,10 @@ public interface StorageLocationReportMXBean { long getRemaining(); + long getCommitted(); + + long getFreeSpaceToSpare(); + String getStorageLocation(); String getStorageTypeName(); diff --git a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/report/CommandStatusReportPublisher.java b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/report/CommandStatusReportPublisher.java index 19fde7146861..ee08c9f79dbc 100644 --- a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/report/CommandStatusReportPublisher.java +++ b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/report/CommandStatusReportPublisher.java @@ -78,9 +78,9 @@ protected CommandStatusReportsProto getReport() { // If status is still pending then don't remove it from map as // CommandHandler will change its status when it works on this command. if (!cmdStatus.getStatus().equals(Status.PENDING)) { - builder.addCmdStatus(cmdStatus.getProtoBufMessage()); map.remove(key); } + builder.addCmdStatus(cmdStatus.getProtoBufMessage()); }); return builder.getCmdStatusCount() > 0 ? builder.build() : null; } diff --git a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/statemachine/DatanodeConfiguration.java b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/statemachine/DatanodeConfiguration.java index d7a3e391c921..a8b0d8cfa4bc 100644 --- a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/statemachine/DatanodeConfiguration.java +++ b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/statemachine/DatanodeConfiguration.java @@ -184,7 +184,11 @@ public class DatanodeConfiguration extends ReconfigurableConfig { defaultValue = "5", tags = {DATANODE}, description = "The maximum number of block delete commands queued on " + - " a datanode" + " a datanode, This configuration is also used by the SCM to " + + "control whether to send delete commands to the DN. If the DN" + + " has more commands waiting in the queue than this value, " + + "the SCM will not send any new block delete commands. until the " + + "DN has processed some commands and the queue length is reduced." ) private int blockDeleteQueueLimit = 5; diff --git a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/statemachine/DatanodeStateMachine.java b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/statemachine/DatanodeStateMachine.java index 51290cf80df8..967714405491 100644 --- a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/statemachine/DatanodeStateMachine.java +++ b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/statemachine/DatanodeStateMachine.java @@ -250,8 +250,8 @@ public DatanodeStateMachine(DatanodeDetails datanodeDetails, .addHandler(new DeleteContainerCommandHandler( dnConf.getContainerDeleteThreads(), clock, dnConf.getCommandQueueLimit(), threadNamePrefix)) - .addHandler( - new ClosePipelineCommandHandler(pipelineCommandExecutorService)) + .addHandler(new ClosePipelineCommandHandler(conf, + pipelineCommandExecutorService)) .addHandler(new CreatePipelineCommandHandler(conf, pipelineCommandExecutorService)) .addHandler(new SetNodeOperationalStateCommandHandler(conf, diff --git a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/statemachine/commandhandler/ClosePipelineCommandHandler.java b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/statemachine/commandhandler/ClosePipelineCommandHandler.java index f332ad4f1343..241abb6f4ae1 100644 --- a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/statemachine/commandhandler/ClosePipelineCommandHandler.java +++ b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/statemachine/commandhandler/ClosePipelineCommandHandler.java @@ -16,28 +16,39 @@ */ package org.apache.hadoop.ozone.container.common.statemachine.commandhandler; +import org.apache.hadoop.hdds.conf.ConfigurationSource; import org.apache.hadoop.hdds.protocol.DatanodeDetails; import org.apache.hadoop.hdds.protocol.proto.HddsProtos; import org.apache.hadoop.hdds.protocol.proto. StorageContainerDatanodeProtocolProtos.SCMCommandProto; +import org.apache.hadoop.hdds.ratis.RatisHelper; +import org.apache.hadoop.hdds.scm.client.HddsClientUtils; import org.apache.hadoop.hdds.scm.pipeline.PipelineID; import org.apache.hadoop.ozone.container.common.statemachine .SCMConnectionManager; import org.apache.hadoop.ozone.container.common.statemachine.StateContext; import org.apache.hadoop.ozone.container.common.transport.server .XceiverServerSpi; +import org.apache.hadoop.ozone.container.common.transport.server.ratis.XceiverServerRatis; import org.apache.hadoop.ozone.container.ozoneimpl.OzoneContainer; import org.apache.hadoop.ozone.protocol.commands.ClosePipelineCommand; import org.apache.hadoop.ozone.protocol.commands.SCMCommand; import org.apache.hadoop.util.Time; +import org.apache.ratis.client.RaftClient; +import org.apache.ratis.grpc.GrpcTlsConfig; +import org.apache.ratis.protocol.RaftGroupId; +import org.apache.ratis.protocol.RaftPeer; +import org.apache.ratis.protocol.exceptions.GroupMismatchException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; +import java.util.Collection; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; +import java.util.function.BiFunction; /** * Handler for close pipeline command received from SCM. @@ -51,11 +62,23 @@ public class ClosePipelineCommandHandler implements CommandHandler { private final AtomicInteger queuedCount = new AtomicInteger(0); private long totalTime; private final Executor executor; + private final BiFunction newRaftClient; /** * Constructs a closePipelineCommand handler. */ - public ClosePipelineCommandHandler(Executor executor) { + public ClosePipelineCommandHandler(ConfigurationSource conf, + Executor executor) { + this(RatisHelper.newRaftClientNoRetry(conf), executor); + } + + /** + * Constructs a closePipelineCommand handler. + */ + public ClosePipelineCommandHandler( + BiFunction newRaftClient, + Executor executor) { + this.newRaftClient = newRaftClient; this.executor = executor; } @@ -83,15 +106,53 @@ public void handle(SCMCommand command, OzoneContainer ozoneContainer, try { XceiverServerSpi server = ozoneContainer.getWriteChannel(); if (server.isExist(pipelineIdProto)) { + if (server instanceof XceiverServerRatis) { + // TODO: Refactor Ratis logic to XceiverServerRatis + // Propagate the group remove to the other Raft peers in the pipeline + XceiverServerRatis ratisServer = (XceiverServerRatis) server; + final RaftGroupId raftGroupId = RaftGroupId.valueOf(pipelineID.getId()); + final boolean shouldDeleteRatisLogDirectory = ratisServer.getShouldDeleteRatisLogDirectory(); + // This might throw GroupMismatchException if the Ratis group has been closed by other datanodes + final Collection peers = ratisServer.getRaftPeersInPipeline(pipelineID); + // Try to send remove group for the other datanodes first, ignoring GroupMismatchException + // if the Ratis group has been closed in the other datanodes + peers.stream() + .filter(peer -> !peer.getId().equals(ratisServer.getServer().getId())) + .forEach(peer -> { + try (RaftClient client = newRaftClient.apply(peer, ozoneContainer.getTlsClientConfig())) { + client.getGroupManagementApi(peer.getId()) + .remove(raftGroupId, shouldDeleteRatisLogDirectory, !shouldDeleteRatisLogDirectory); + } catch (GroupMismatchException ae) { + // ignore silently since this means that the group has been closed by earlier close pipeline + // command in another datanode + LOG.debug("Failed to remove group {} for pipeline {} on peer {} since the group has " + + "been removed by earlier close pipeline command handled in another datanode", raftGroupId, + pipelineID, peer.getId()); + } catch (IOException ioe) { + LOG.warn("Failed to remove group {} of pipeline {} on peer {}", + raftGroupId, pipelineID, peer.getId(), ioe); + } + }); + } + // Remove the Ratis group from the current datanode pipeline, might throw GroupMismatchException as + // well. It is a no-op for XceiverServerSpi implementations (e.g. XceiverServerGrpc) server.removeGroup(pipelineIdProto); LOG.info("Close Pipeline {} command on datanode {}.", pipelineID, dn.getUuidString()); } else { - LOG.debug("Ignoring close pipeline command for pipeline {} " + - "as it does not exist", pipelineID); + LOG.debug("Ignoring close pipeline command for pipeline {} on datanode {} " + + "as it does not exist", pipelineID, dn.getUuidString()); } } catch (IOException e) { - LOG.error("Can't close pipeline {}", pipelineID, e); + Throwable gme = HddsClientUtils.containsException(e, GroupMismatchException.class); + if (gme != null) { + // ignore silently since this means that the group has been closed by earlier close pipeline + // command in another datanode + LOG.debug("The group for pipeline {} on datanode {} has been removed by earlier close " + + "pipeline command handled in another datanode", pipelineID, dn.getUuidString()); + } else { + LOG.error("Can't close pipeline {}", pipelineID, e); + } } finally { long endTime = Time.monotonicNow(); totalTime += endTime - startTime; diff --git a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/transport/server/ratis/XceiverServerRatis.java b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/transport/server/ratis/XceiverServerRatis.java index c47f80f3b421..e6cbe262f6a6 100644 --- a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/transport/server/ratis/XceiverServerRatis.java +++ b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/transport/server/ratis/XceiverServerRatis.java @@ -83,6 +83,7 @@ import org.apache.ratis.netty.NettyConfigKeys; import org.apache.ratis.proto.RaftProtos; import org.apache.ratis.proto.RaftProtos.RoleInfoProto; +import org.apache.ratis.protocol.RaftPeer; import org.apache.ratis.protocol.exceptions.NotLeaderException; import org.apache.ratis.protocol.exceptions.StateMachineException; import org.apache.ratis.protocol.ClientId; @@ -622,6 +623,10 @@ public RaftServer.Division getServerDivision(RaftGroupId id) return server.getDivision(id); } + public boolean getShouldDeleteRatisLogDirectory() { + return this.shouldDeleteRatisLogDirectory; + } + private void processReply(RaftClientReply reply) throws IOException { // NotLeader exception is thrown only when the raft server to which the // request is submitted is not the leader. The request will be rejected @@ -919,6 +924,11 @@ public long getMinReplicatedIndex(PipelineID pipelineID) throws IOException { return minIndex == null ? -1 : minIndex; } + public Collection getRaftPeersInPipeline(PipelineID pipelineId) throws IOException { + final RaftGroupId groupId = RaftGroupId.valueOf(pipelineId.getId()); + return server.getDivision(groupId).getGroup().getPeers(); + } + public void notifyGroupRemove(RaftGroupId gid) { raftGids.remove(gid); // Remove any entries for group leader map diff --git a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/AvailableSpaceFilter.java b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/AvailableSpaceFilter.java index 13041eb4d662..622c85a52fa0 100644 --- a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/AvailableSpaceFilter.java +++ b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/AvailableSpaceFilter.java @@ -25,7 +25,7 @@ * Filter for selecting volumes with enough space for a new container. * Keeps track of ineligible volumes for logging/debug purposes. */ -class AvailableSpaceFilter implements Predicate { +public class AvailableSpaceFilter implements Predicate { private final long requiredSpace; private final Map fullVolumes = @@ -42,10 +42,10 @@ public boolean test(HddsVolume vol) { long free = vol.getAvailable(); long committed = vol.getCommittedBytes(); long available = free - committed; - long volumeFreeSpace = + long volumeFreeSpaceToSpare = VolumeUsage.getMinVolumeFreeSpace(vol.getConf(), volumeCapacity); - boolean hasEnoughSpace = - available > Math.max(requiredSpace, volumeFreeSpace); + boolean hasEnoughSpace = VolumeUsage.hasVolumeEnoughSpace(free, committed, + requiredSpace, volumeFreeSpaceToSpare); mostAvailableSpace = Math.max(available, mostAvailableSpace); diff --git a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/HddsVolume.java b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/HddsVolume.java index 581e5aaa4334..44bd4cf19a46 100644 --- a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/HddsVolume.java +++ b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/HddsVolume.java @@ -79,7 +79,7 @@ public class HddsVolume extends StorageVolume { private final VolumeIOStats volumeIOStats; private final VolumeInfoMetrics volumeInfoMetrics; - private final AtomicLong committedBytes; // till Open containers become full + private final AtomicLong committedBytes = new AtomicLong(); // till Open containers become full // Mentions the type of volume private final VolumeType type = VolumeType.DATA_VOLUME; @@ -121,7 +121,6 @@ private HddsVolume(Builder b) throws IOException { this.getStorageDir().toString()); this.volumeInfoMetrics = new VolumeInfoMetrics(b.getVolumeRootStr(), this); - this.committedBytes = new AtomicLong(0); LOG.info("Creating HddsVolume: {} of storage type : {} capacity : {}", getStorageDir(), b.getStorageType(), @@ -134,7 +133,6 @@ private HddsVolume(Builder b) throws IOException { this.setState(VolumeState.FAILED); volumeIOStats = null; volumeInfoMetrics = new VolumeInfoMetrics(b.getVolumeRootStr(), this); - committedBytes = null; } } diff --git a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/MutableVolumeSet.java b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/MutableVolumeSet.java index 985ddea8deb8..3c0b6e618ee1 100644 --- a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/MutableVolumeSet.java +++ b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/MutableVolumeSet.java @@ -464,6 +464,7 @@ public StorageLocationReport[] getStorageReport() { long scmUsed = 0; long remaining = 0; long capacity = 0; + long committed = 0; String rootDir = ""; failed = true; if (volumeInfo.isPresent()) { @@ -472,6 +473,8 @@ public StorageLocationReport[] getStorageReport() { scmUsed = volumeInfo.get().getScmUsed(); remaining = volumeInfo.get().getAvailable(); capacity = volumeInfo.get().getCapacity(); + committed = (volume instanceof HddsVolume) ? + ((HddsVolume) volume).getCommittedBytes() : 0; failed = false; } catch (UncheckedIOException ex) { LOG.warn("Failed to get scmUsed and remaining for container " + @@ -491,6 +494,7 @@ public StorageLocationReport[] getStorageReport() { .setCapacity(capacity) .setRemaining(remaining) .setScmUsed(scmUsed) + .setCommitted(committed) .setStorageType(volume.getStorageType()); StorageLocationReport r = builder.build(); reports[counter++] = r; diff --git a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/StorageVolume.java b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/StorageVolume.java index fc329ebeb0ea..61c6cb053111 100644 --- a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/StorageVolume.java +++ b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/StorageVolume.java @@ -23,6 +23,7 @@ import org.apache.hadoop.fs.StorageType; import org.apache.hadoop.hdds.conf.ConfigurationSource; import org.apache.hadoop.hdds.fs.SpaceUsageCheckFactory; +import org.apache.hadoop.hdds.fs.SpaceUsageSource; import org.apache.hadoop.hdfs.server.datanode.StorageLocation; import org.apache.hadoop.hdfs.server.datanode.checker.Checkable; import org.apache.hadoop.hdfs.server.datanode.checker.VolumeCheckResult; @@ -50,7 +51,6 @@ import java.util.stream.Stream; import static org.apache.hadoop.ozone.container.common.HDDSVolumeLayoutVersion.getLatestVersion; -import static org.apache.hadoop.ozone.container.common.volume.VolumeUsage.PrecomputedVolumeSpace; /** @@ -456,14 +456,9 @@ public long getAvailable() { } - public long getAvailable(PrecomputedVolumeSpace precomputedVolumeSpace) { - return volumeInfo.map(info -> info.getAvailable(precomputedVolumeSpace)) - .orElse(0L); - } - - public PrecomputedVolumeSpace getPrecomputedVolumeSpace() { - return volumeInfo.map(VolumeInfo::getPrecomputedVolumeSpace) - .orElse(new PrecomputedVolumeSpace(0L, 0L)); + public SpaceUsageSource getCurrentUsage() { + return volumeInfo.map(VolumeInfo::getCurrentUsage) + .orElse(SpaceUsageSource.UNKNOWN); } public long getUsedSpace() { diff --git a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/VolumeInfo.java b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/VolumeInfo.java index 61c0b422c7fa..af890269255d 100644 --- a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/VolumeInfo.java +++ b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/VolumeInfo.java @@ -20,23 +20,17 @@ import java.io.File; import java.io.IOException; -import java.util.Collection; import org.apache.hadoop.fs.StorageType; import org.apache.hadoop.hdds.conf.ConfigurationSource; -import org.apache.hadoop.hdds.conf.StorageSize; import org.apache.hadoop.hdds.fs.SpaceUsageCheckFactory; import org.apache.hadoop.hdds.fs.SpaceUsageCheckParams; import com.google.common.annotations.VisibleForTesting; +import org.apache.hadoop.hdds.fs.SpaceUsageSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static org.apache.hadoop.ozone.container.common.volume.VolumeUsage.PrecomputedVolumeSpace; -import static org.apache.hadoop.hdds.scm.ScmConfigKeys.HDDS_DATANODE_DIR_DU_RESERVED; -import static org.apache.hadoop.hdds.scm.ScmConfigKeys.HDDS_DATANODE_DIR_DU_RESERVED_PERCENT; -import static org.apache.hadoop.hdds.scm.ScmConfigKeys.HDDS_DATANODE_DIR_DU_RESERVED_PERCENT_DEFAULT; - /** * Stores information about a disk/volume. * @@ -100,13 +94,6 @@ public final class VolumeInfo { // Space usage calculator private final VolumeUsage usage; - // Capacity configured. This is useful when we want to - // limit the visible capacity for tests. If negative, then we just - // query from the filesystem. - private long configuredCapacity; - - private long reservedInBytes; - /** * Builder for VolumeInfo. */ @@ -115,7 +102,6 @@ public static class Builder { private final String rootDir; private SpaceUsageCheckFactory usageCheckFactory; private StorageType storageType; - private long configuredCapacity; public Builder(String root, ConfigurationSource config) { this.rootDir = root; @@ -127,11 +113,6 @@ public Builder storageType(StorageType st) { return this; } - public Builder configuredCapacity(long capacity) { - this.configuredCapacity = capacity; - return this; - } - public Builder usageCheckFactory(SpaceUsageCheckFactory factory) { this.usageCheckFactory = factory; return this; @@ -142,55 +123,6 @@ public VolumeInfo build() throws IOException { } } - private long getReserved(ConfigurationSource conf) { - if (conf.isConfigured(HDDS_DATANODE_DIR_DU_RESERVED_PERCENT) - && conf.isConfigured(HDDS_DATANODE_DIR_DU_RESERVED)) { - LOG.error("Both {} and {} are set. Set either one, not both. If the " + - "volume matches with volume parameter in former config, it is set " + - "as reserved space. If not it fall backs to the latter config.", - HDDS_DATANODE_DIR_DU_RESERVED, HDDS_DATANODE_DIR_DU_RESERVED_PERCENT); - } - - // 1. If hdds.datanode.dir.du.reserved is set for a volume then make it - // as the reserved bytes. - Collection reserveList = conf.getTrimmedStringCollection( - HDDS_DATANODE_DIR_DU_RESERVED); - for (String reserve : reserveList) { - String[] words = reserve.split(":"); - if (words.length < 2) { - LOG.error("Reserved space should config in pair, but current is {}", - reserve); - continue; - } - - if (words[0].trim().equals(rootDir)) { - try { - StorageSize size = StorageSize.parse(words[1].trim()); - return (long) size.getUnit().toBytes(size.getValue()); - } catch (Exception e) { - LOG.error("Failed to parse StorageSize: {}", words[1].trim(), e); - break; - } - } - } - - // 2. If hdds.datanode.dir.du.reserved not set and - // hdds.datanode.dir.du.reserved.percent is set, fall back to this config. - if (conf.isConfigured(HDDS_DATANODE_DIR_DU_RESERVED_PERCENT)) { - float percentage = conf.getFloat(HDDS_DATANODE_DIR_DU_RESERVED_PERCENT, - HDDS_DATANODE_DIR_DU_RESERVED_PERCENT_DEFAULT); - if (0 <= percentage && percentage <= 1) { - return (long) Math.ceil(this.usage.getCapacity() * percentage); - } - //If it comes here then the percentage is not between 0-1. - LOG.error("The value of {} should be between 0 to 1. Defaulting to 0.", - HDDS_DATANODE_DIR_DU_RESERVED_PERCENT); - } - - //Both configs are not set, return 0. - return 0; - } - private VolumeInfo(Builder b) throws IOException { this.rootDir = b.rootDir; @@ -206,9 +138,6 @@ private VolumeInfo(Builder b) throws IOException { this.storageType = (b.storageType != null ? b.storageType : StorageType.DEFAULT); - this.configuredCapacity = (b.configuredCapacity != 0 ? - b.configuredCapacity : -1); - SpaceUsageCheckFactory usageCheckFactory = b.usageCheckFactory; if (usageCheckFactory == null) { usageCheckFactory = SpaceUsageCheckFactory.create(b.conf); @@ -216,16 +145,11 @@ private VolumeInfo(Builder b) throws IOException { SpaceUsageCheckParams checkParams = usageCheckFactory.paramsFor(root); - this.usage = new VolumeUsage(checkParams); - this.reservedInBytes = getReserved(b.conf); - this.usage.setReserved(reservedInBytes); + usage = new VolumeUsage(checkParams, b.conf); } public long getCapacity() { - if (configuredCapacity < 0) { - return Math.max(usage.getCapacity() - reservedInBytes, 0); - } - return configuredCapacity; + return usage.getCapacity(); } /** @@ -236,17 +160,11 @@ public long getCapacity() { * A) avail = capacity - used */ public long getAvailable() { - long avail = getCapacity() - usage.getUsedSpace(); - return Math.max(Math.min(avail, usage.getAvailable()), 0); - } - - public long getAvailable(PrecomputedVolumeSpace precomputedValues) { - long avail = precomputedValues.getCapacity() - usage.getUsedSpace(); - return Math.max(Math.min(avail, usage.getAvailable(precomputedValues)), 0); + return usage.getAvailable(); } - public PrecomputedVolumeSpace getPrecomputedVolumeSpace() { - return usage.getPrecomputedVolumeSpace(); + public SpaceUsageSource getCurrentUsage() { + return usage.getCurrentUsage(); } public void incrementUsedSpace(long usedSpace) { @@ -285,8 +203,7 @@ public VolumeUsage getUsageForTesting() { return usage; } - @VisibleForTesting public long getReservedInBytes() { - return reservedInBytes; + return usage.getReservedBytes(); } } diff --git a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/VolumeInfoMetrics.java b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/VolumeInfoMetrics.java index c90dcea81ff2..18e7354ec1da 100644 --- a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/VolumeInfoMetrics.java +++ b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/VolumeInfoMetrics.java @@ -142,4 +142,9 @@ public long getTotalCapacity() { return (getUsed() + getAvailable() + getReserved()); } + @Metric("Returns the Committed bytes of the Volume") + public long getCommitted() { + return volume.getCommittedBytes(); + } + } diff --git a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/VolumeUsage.java b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/VolumeUsage.java index e7a06abc9e36..d18998821b1e 100644 --- a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/VolumeUsage.java +++ b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/volume/VolumeUsage.java @@ -18,38 +18,62 @@ package org.apache.hadoop.ozone.container.common.volume; +import com.google.common.annotations.VisibleForTesting; import org.apache.hadoop.hdds.conf.ConfigurationSource; +import org.apache.hadoop.hdds.conf.StorageSize; import org.apache.hadoop.hdds.conf.StorageUnit; import org.apache.hadoop.hdds.fs.CachingSpaceUsageSource; import org.apache.hadoop.hdds.fs.SpaceUsageCheckParams; import org.apache.hadoop.hdds.fs.SpaceUsageSource; +import org.apache.ratis.util.Preconditions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.File; +import java.io.IOException; +import java.util.Collection; + import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_DATANODE_VOLUME_MIN_FREE_SPACE; import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_DATANODE_VOLUME_MIN_FREE_SPACE_DEFAULT; import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_DATANODE_VOLUME_MIN_FREE_SPACE_PERCENT; +import static org.apache.hadoop.hdds.scm.ScmConfigKeys.HDDS_DATANODE_DIR_DU_RESERVED; +import static org.apache.hadoop.hdds.scm.ScmConfigKeys.HDDS_DATANODE_DIR_DU_RESERVED_PERCENT; +import static org.apache.hadoop.hdds.scm.ScmConfigKeys.HDDS_DATANODE_DIR_DU_RESERVED_PERCENT_DEFAULT; /** * Class that wraps the space df of the Datanode Volumes used by SCM * containers. */ -public class VolumeUsage implements SpaceUsageSource { +public class VolumeUsage { private final CachingSpaceUsageSource source; private boolean shutdownComplete; - private long reservedInBytes; + private final long reservedInBytes; private static final Logger LOG = LoggerFactory.getLogger(VolumeUsage.class); - VolumeUsage(SpaceUsageCheckParams checkParams) { + VolumeUsage(SpaceUsageCheckParams checkParams, ConfigurationSource conf) { source = new CachingSpaceUsageSource(checkParams); + reservedInBytes = getReserved(conf, checkParams.getPath(), source.getCapacity()); + Preconditions.assertTrue(reservedInBytes >= 0, reservedInBytes + " < 0"); start(); // TODO should start only on demand } - @Override + @VisibleForTesting + SpaceUsageSource realUsage() { + return source.snapshot(); + } + public long getCapacity() { - return Math.max(source.getCapacity(), 0); + return getCurrentUsage().getCapacity(); + } + + public long getAvailable() { + return getCurrentUsage().getAvailable(); + } + + public long getUsedSpace() { + return getCurrentUsage().getUsedSpace(); } /** @@ -60,19 +84,15 @@ public long getCapacity() { * remainingReserved * B) avail = fsAvail - Max(reserved - other, 0); */ - @Override - public long getAvailable() { - return source.getAvailable() - getRemainingReserved(); - } + public SpaceUsageSource getCurrentUsage() { + SpaceUsageSource real = realUsage(); - public long getAvailable(PrecomputedVolumeSpace precomputedVolumeSpace) { - long available = precomputedVolumeSpace.getAvailable(); - return available - getRemainingReserved(precomputedVolumeSpace); - } - - @Override - public long getUsedSpace() { - return source.getUsedSpace(); + return reservedInBytes == 0 + ? real + : new SpaceUsageSource.Fixed( + Math.max(real.getCapacity() - reservedInBytes, 0), + Math.max(real.getAvailable() - getRemainingReserved(real), 0), + real.getUsedSpace()); } public void incrementUsedSpace(long usedSpace) { @@ -89,23 +109,14 @@ public void decrementUsedSpace(long reclaimedSpace) { * so there could be that DU value > totalUsed when there are deletes. * @return other used space */ - private long getOtherUsed() { - long totalUsed = source.getCapacity() - source.getAvailable(); - return Math.max(totalUsed - source.getUsedSpace(), 0L); - } - - private long getOtherUsed(PrecomputedVolumeSpace precomputedVolumeSpace) { + private static long getOtherUsed(SpaceUsageSource precomputedVolumeSpace) { long totalUsed = precomputedVolumeSpace.getCapacity() - precomputedVolumeSpace.getAvailable(); - return Math.max(totalUsed - source.getUsedSpace(), 0L); - } - - private long getRemainingReserved() { - return Math.max(reservedInBytes - getOtherUsed(), 0L); + return Math.max(totalUsed - precomputedVolumeSpace.getUsedSpace(), 0L); } private long getRemainingReserved( - PrecomputedVolumeSpace precomputedVolumeSpace) { + SpaceUsageSource precomputedVolumeSpace) { return Math.max(reservedInBytes - getOtherUsed(precomputedVolumeSpace), 0L); } @@ -124,8 +135,8 @@ public void refreshNow() { source.refreshNow(); } - public void setReserved(long reserved) { - this.reservedInBytes = reserved; + public long getReservedBytes() { + return reservedInBytes; } /** @@ -162,32 +173,59 @@ public static long getMinVolumeFreeSpace(ConfigurationSource conf, } - /** - * Class representing precomputed space values of a volume. - * This class is intended to store precomputed values, such as capacity - * and available space of a volume, to avoid recalculating these - * values multiple times and to make method signatures simpler. - */ - public static class PrecomputedVolumeSpace { - private final long capacity; - private final long available; + public static boolean hasVolumeEnoughSpace(long volumeAvailableSpace, + long volumeCommittedBytesCount, + long requiredSpace, + long volumeFreeSpaceToSpare) { + return (volumeAvailableSpace - volumeCommittedBytesCount) > + Math.max(requiredSpace, volumeFreeSpaceToSpare); + } - public PrecomputedVolumeSpace(long capacity, long available) { - this.capacity = capacity; - this.available = available; + private static long getReserved(ConfigurationSource conf, String rootDir, + long capacity) { + if (conf.isConfigured(HDDS_DATANODE_DIR_DU_RESERVED_PERCENT) + && conf.isConfigured(HDDS_DATANODE_DIR_DU_RESERVED)) { + LOG.error("Both {} and {} are set. Set either one, not both. If the " + + "volume matches with volume parameter in former config, it is set " + + "as reserved space. If not it fall backs to the latter config.", + HDDS_DATANODE_DIR_DU_RESERVED, HDDS_DATANODE_DIR_DU_RESERVED_PERCENT); } - public long getCapacity() { - return capacity; + // 1. If hdds.datanode.dir.du.reserved is set for a volume then make it + // as the reserved bytes. + Collection reserveList = conf.getTrimmedStringCollection( + HDDS_DATANODE_DIR_DU_RESERVED); + for (String reserve : reserveList) { + String[] words = reserve.split(":"); + if (words.length < 2) { + LOG.error("Reserved space should be configured in a pair, but current value is {}", + reserve); + continue; + } + + try { + String path = new File(words[0]).getCanonicalPath(); + if (path.equals(rootDir)) { + StorageSize size = StorageSize.parse(words[1].trim()); + return (long) size.getUnit().toBytes(size.getValue()); + } + } catch (IllegalArgumentException e) { + LOG.error("Failed to parse StorageSize {} from config {}", words[1].trim(), HDDS_DATANODE_DIR_DU_RESERVED, e); + } catch (IOException e) { + LOG.error("Failed to read storage path {} from config {}", words[1].trim(), HDDS_DATANODE_DIR_DU_RESERVED, e); + } } - public long getAvailable() { - return available; + // 2. If hdds.datanode.dir.du.reserved not set then fall back to hdds.datanode.dir.du.reserved.percent, using + // either its set value or default value if it has not been set. + float percentage = conf.getFloat(HDDS_DATANODE_DIR_DU_RESERVED_PERCENT, + HDDS_DATANODE_DIR_DU_RESERVED_PERCENT_DEFAULT); + if (percentage < 0 || percentage > 1) { + LOG.error("The value of {} should be between 0 to 1. Falling back to default value {}", + HDDS_DATANODE_DIR_DU_RESERVED_PERCENT, HDDS_DATANODE_DIR_DU_RESERVED_PERCENT_DEFAULT); + percentage = HDDS_DATANODE_DIR_DU_RESERVED_PERCENT_DEFAULT; } - } - public PrecomputedVolumeSpace getPrecomputedVolumeSpace() { - return new PrecomputedVolumeSpace(source.getCapacity(), - source.getAvailable()); + return (long) Math.ceil(capacity * percentage); } } diff --git a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/ec/reconstruction/ECReconstructionCoordinator.java b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/ec/reconstruction/ECReconstructionCoordinator.java index 62f96d8adf00..8fadd19b67d3 100644 --- a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/ec/reconstruction/ECReconstructionCoordinator.java +++ b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/ec/reconstruction/ECReconstructionCoordinator.java @@ -259,12 +259,15 @@ public void reconstructECBlockGroup(BlockLocationInfo blockLocationInfo, } } + OzoneClientConfig clientConfig = this.ozoneClientConfig; + clientConfig.setChecksumVerify(true); try (ECBlockReconstructedStripeInputStream sis = new ECBlockReconstructedStripeInputStream( - repConfig, blockLocationInfo, true, + repConfig, blockLocationInfo, this.containerOperationClient.getXceiverClientManager(), null, this.blockInputStreamFactory, byteBufferPool, - this.ecReconstructReadExecutor)) { + this.ecReconstructReadExecutor, + clientConfig)) { ECBlockOutputStream[] targetBlockStreams = new ECBlockOutputStream[toReconstructIndexes.size()]; diff --git a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/keyvalue/KeyValueHandler.java b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/keyvalue/KeyValueHandler.java index a501b663d126..c462649c68c7 100644 --- a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/keyvalue/KeyValueHandler.java +++ b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/keyvalue/KeyValueHandler.java @@ -115,6 +115,7 @@ import static org.apache.hadoop.hdds.scm.utils.ClientCommandsUtils.getReadChunkVersion; import static org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos .ContainerDataProto.State.RECOVERING; +import static org.apache.hadoop.ozone.ClientVersion.EC_REPLICA_INDEX_REQUIRED_IN_BLOCK_REQUEST; import static org.apache.hadoop.ozone.container.common.interfaces.Container.ScanResult; import org.apache.ratis.statemachine.StateMachine; @@ -562,6 +563,15 @@ ContainerCommandResponseProto handlePutBlock( return putBlockResponseSuccess(request, blockDataProto); } + /** + * Checks if a replicaIndex needs to be checked based on the client version for a request. + * @param request ContainerCommandRequest object. + * @return true if the validation is required for the client version else false. + */ + private boolean replicaIndexCheckRequired(ContainerCommandRequestProto request) { + return request.hasVersion() && request.getVersion() >= EC_REPLICA_INDEX_REQUIRED_IN_BLOCK_REQUEST.toProtoValue(); + } + /** * Handle Get Block operation. Calls BlockManager to process the request. */ @@ -580,9 +590,10 @@ ContainerCommandResponseProto handleGetBlock( try { BlockID blockID = BlockID.getFromProtobuf( request.getGetBlock().getBlockID()); - checkContainerIsHealthy(kvContainer, blockID, Type.GetBlock); - responseData = blockManager.getBlock(kvContainer, blockID) - .getProtoBufMessage(); + if (replicaIndexCheckRequired(request)) { + BlockUtils.verifyReplicaIdx(kvContainer, blockID); + } + responseData = blockManager.getBlock(kvContainer, blockID).getProtoBufMessage(); final long numBytes = responseData.getSerializedSize(); metrics.incContainerBytesStats(Type.GetBlock, numBytes); @@ -615,8 +626,6 @@ ContainerCommandResponseProto handleGetCommittedBlockLength( try { BlockID blockID = BlockID .getFromProtobuf(request.getGetCommittedBlockLength().getBlockID()); - checkContainerIsHealthy(kvContainer, blockID, - Type.GetCommittedBlockLength); BlockUtils.verifyBCSId(kvContainer, blockID); blockLength = blockManager.getCommittedBlockLength(kvContainer, blockID); } catch (StorageContainerException ex) { @@ -686,7 +695,6 @@ ContainerCommandResponseProto handleDeleteBlock( ContainerCommandResponseProto handleReadChunk( ContainerCommandRequestProto request, KeyValueContainer kvContainer, DispatcherContext dispatcherContext) { - if (!request.hasReadChunk()) { if (LOG.isDebugEnabled()) { LOG.debug("Malformed Read Chunk request. trace ID: {}", @@ -694,7 +702,6 @@ ContainerCommandResponseProto handleReadChunk( } return malformedRequest(request); } - ChunkBuffer data; try { BlockID blockID = BlockID.getFromProtobuf( @@ -702,9 +709,11 @@ ContainerCommandResponseProto handleReadChunk( ChunkInfo chunkInfo = ChunkInfo.getFromProtoBuf(request.getReadChunk() .getChunkData()); Preconditions.checkNotNull(chunkInfo); - - checkContainerIsHealthy(kvContainer, blockID, Type.ReadChunk); + if (replicaIndexCheckRequired(request)) { + BlockUtils.verifyReplicaIdx(kvContainer, blockID); + } BlockUtils.verifyBCSId(kvContainer, blockID); + if (dispatcherContext == null) { dispatcherContext = DispatcherContext.getHandleReadChunk(); } @@ -741,25 +750,6 @@ ContainerCommandResponseProto handleReadChunk( return getReadChunkResponse(request, data, byteBufferToByteString); } - /** - * Throw an exception if the container is unhealthy. - * - * @throws StorageContainerException if the container is unhealthy. - */ - @VisibleForTesting - void checkContainerIsHealthy(KeyValueContainer kvContainer, BlockID blockID, - Type cmd) { - kvContainer.readLock(); - try { - if (kvContainer.getContainerData().getState() == State.UNHEALTHY) { - LOG.warn("{} request {} for UNHEALTHY container {} replica", cmd, - blockID, kvContainer.getContainerData().getContainerID()); - } - } finally { - kvContainer.readUnlock(); - } - } - /** * Handle Delete Chunk operation. Calls ChunkManager to process the request. */ @@ -923,7 +913,6 @@ ContainerCommandResponseProto handleGetSmallFile( try { BlockID blockID = BlockID.getFromProtobuf(getSmallFileReq.getBlock() .getBlockID()); - checkContainerIsHealthy(kvContainer, blockID, Type.GetSmallFile); BlockData responseData = blockManager.getBlock(kvContainer, blockID); ContainerProtos.ChunkInfo chunkInfoProto = null; diff --git a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/keyvalue/helpers/BlockUtils.java b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/keyvalue/helpers/BlockUtils.java index 376285c4c72a..7773b54f7942 100644 --- a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/keyvalue/helpers/BlockUtils.java +++ b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/keyvalue/helpers/BlockUtils.java @@ -28,12 +28,12 @@ import org.apache.hadoop.hdds.scm.container.common.helpers.StorageContainerException; import org.apache.hadoop.ozone.OzoneConsts; import org.apache.hadoop.ozone.container.common.helpers.BlockData; +import org.apache.hadoop.ozone.container.common.interfaces.Container; import org.apache.hadoop.ozone.container.common.interfaces.DBHandle; import org.apache.hadoop.ozone.container.common.utils.ContainerCache; import org.apache.hadoop.ozone.container.common.utils.DatanodeStoreCache; import org.apache.hadoop.ozone.container.common.utils.RawDB; import org.apache.hadoop.ozone.container.common.utils.ReferenceCountedDB; -import org.apache.hadoop.ozone.container.keyvalue.KeyValueContainer; import org.apache.hadoop.ozone.container.keyvalue.KeyValueContainerData; import com.google.common.base.Preconditions; @@ -42,6 +42,7 @@ import org.apache.hadoop.ozone.container.metadata.DatanodeStoreSchemaThreeImpl; import org.apache.hadoop.ozone.container.metadata.DatanodeStoreSchemaTwoImpl; +import static org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.Result.CONTAINER_NOT_FOUND; import static org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.Result.EXPORT_CONTAINER_METADATA_FAILED; import static org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.Result.IMPORT_CONTAINER_METADATA_FAILED; import static org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.Result.NO_SUCH_BLOCK; @@ -220,7 +221,7 @@ public static BlockData getBlockData(byte[] bytes) throws IOException { * @param blockID requested block info * @throws IOException if cannot support block's blockCommitSequenceId */ - public static void verifyBCSId(KeyValueContainer container, BlockID blockID) + public static void verifyBCSId(Container container, BlockID blockID) throws IOException { long bcsId = blockID.getBlockCommitSequenceId(); Preconditions.checkNotNull(blockID, @@ -237,6 +238,24 @@ public static void verifyBCSId(KeyValueContainer container, BlockID blockID) } } + /** + * Verify if request's replicaIndex matches with containerData. + * + * @param container container object. + * @param blockID requested block info + * @throws IOException if replicaIndex mismatches. + */ + public static void verifyReplicaIdx(Container container, BlockID blockID) + throws IOException { + Integer containerReplicaIndex = container.getContainerData().getReplicaIndex(); + if (containerReplicaIndex > 0 && !containerReplicaIndex.equals(blockID.getReplicaIndex())) { + throw new StorageContainerException( + "Unable to find the Container with replicaIdx " + blockID.getReplicaIndex() + ". Container " + + container.getContainerData().getContainerID() + " replicaIdx is " + + containerReplicaIndex + ".", CONTAINER_NOT_FOUND); + } + } + /** * Remove container KV metadata from per-disk db store. * @param containerData diff --git a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/keyvalue/impl/BlockManagerImpl.java b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/keyvalue/impl/BlockManagerImpl.java index 62896561f254..9a2cf7c5eacc 100644 --- a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/keyvalue/impl/BlockManagerImpl.java +++ b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/keyvalue/impl/BlockManagerImpl.java @@ -39,7 +39,6 @@ import com.google.common.base.Preconditions; import static org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.Result.BCSID_MISMATCH; import static org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.Result.NO_SUCH_BLOCK; -import static org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.Result.UNKNOWN_BCSID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -208,24 +207,11 @@ public static long persistPutBlock(KeyValueContainer container, } @Override - public BlockData getBlock(Container container, BlockID blockID) - throws IOException { - long bcsId = blockID.getBlockCommitSequenceId(); - Preconditions.checkNotNull(blockID, - "BlockID cannot be null in GetBlock request"); - Preconditions.checkNotNull(container, - "Container cannot be null"); - + public BlockData getBlock(Container container, BlockID blockID) throws IOException { + BlockUtils.verifyBCSId(container, blockID); KeyValueContainerData containerData = (KeyValueContainerData) container .getContainerData(); - long containerBCSId = containerData.getBlockCommitSequenceId(); - if (containerBCSId < bcsId) { - throw new StorageContainerException( - "Unable to find the block with bcsID " + bcsId + " .Container " - + containerData.getContainerID() + " bcsId is " - + containerBCSId + ".", UNKNOWN_BCSID); - } - + long bcsId = blockID.getBlockCommitSequenceId(); try (DBHandle db = BlockUtils.getDB(containerData, config)) { // This is a post condition that acts as a hint to the user. // Should never fail. diff --git a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/keyvalue/interfaces/BlockManager.java b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/keyvalue/interfaces/BlockManager.java index 02b7e93d50f4..3eab64b312ff 100644 --- a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/keyvalue/interfaces/BlockManager.java +++ b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/keyvalue/interfaces/BlockManager.java @@ -58,8 +58,8 @@ long putBlock(Container container, BlockData data, boolean endOfBlock) * @return Block Data. * @throws IOException when BcsId is unknown or mismatched */ - BlockData getBlock(Container container, BlockID blockID) - throws IOException; + BlockData getBlock(Container container, BlockID blockID) throws IOException; + /** * Deletes an existing block. diff --git a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/ozoneimpl/OzoneContainer.java b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/ozoneimpl/OzoneContainer.java index 1e34fb104939..277ab4464e30 100644 --- a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/ozoneimpl/OzoneContainer.java +++ b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/ozoneimpl/OzoneContainer.java @@ -524,7 +524,7 @@ public StorageContainerDatanodeProtocolProtos.NodeReportProto getNodeReport() = StorageContainerDatanodeProtocolProtos. NodeReportProto.newBuilder(); for (int i = 0; i < reports.length; i++) { - nrb.addStorageReport(reports[i].getProtoBufMessage()); + nrb.addStorageReport(reports[i].getProtoBufMessage(config)); } StorageLocationReport[] metaReports = metaVolumeSet.getStorageReport(); diff --git a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/helpers/TestBlockData.java b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/helpers/TestBlockData.java index 4d46b16469f7..d9b0fa075c3d 100644 --- a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/helpers/TestBlockData.java +++ b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/helpers/TestBlockData.java @@ -135,7 +135,7 @@ public void testToString() { final BlockID blockID = new BlockID(5, 123); blockID.setBlockCommitSequenceId(42); final BlockData subject = new BlockData(blockID); - assertEquals("[blockId=conID: 5 locID: 123 bcsId: 42, size=0]", + assertEquals("[blockId=conID: 5 locID: 123 bcsId: 42 replicaIndex: null, size=0]", subject.toString()); } } diff --git a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/impl/TestHddsDispatcher.java b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/impl/TestHddsDispatcher.java index d87f555b947a..aff287ed9524 100644 --- a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/impl/TestHddsDispatcher.java +++ b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/impl/TestHddsDispatcher.java @@ -27,6 +27,7 @@ import org.apache.hadoop.hdds.client.BlockID; import org.apache.hadoop.hdds.conf.OzoneConfiguration; import org.apache.hadoop.hdds.fs.MockSpaceUsageCheckFactory; +import org.apache.hadoop.hdds.fs.MockSpaceUsageSource; import org.apache.hadoop.hdds.fs.SpaceUsageCheckFactory; import org.apache.hadoop.hdds.fs.SpaceUsageSource; import org.apache.hadoop.hdds.protocol.DatanodeDetails; @@ -86,7 +87,6 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static org.apache.hadoop.hdds.fs.MockSpaceUsagePersistence.inMemory; -import static org.apache.hadoop.hdds.fs.MockSpaceUsageSource.fixed; import static org.apache.hadoop.hdds.scm.ScmConfigKeys.HDDS_DATANODE_DIR_KEY; import static org.apache.hadoop.hdds.scm.protocolPB.ContainerCommandResponseBuilders.getContainerCommandResponse; import static org.apache.hadoop.ozone.container.common.ContainerTestUtils.COMMIT_STAGE; @@ -258,7 +258,8 @@ public void testContainerCloseActionWhenVolumeFull() throws Exception { .conf(conf).usageCheckFactory(MockSpaceUsageCheckFactory.NONE); // state of cluster : available (140) > 100 ,datanode volume // utilisation threshold not yet reached. container creates are successful. - SpaceUsageSource spaceUsage = fixed(500, 140, 360); + AtomicLong usedSpace = new AtomicLong(360); + SpaceUsageSource spaceUsage = MockSpaceUsageSource.of(500, usedSpace); SpaceUsageCheckFactory factory = MockSpaceUsageCheckFactory.of( spaceUsage, Duration.ZERO, inMemory(new AtomicLong(0))); @@ -292,6 +293,7 @@ public void testContainerCloseActionWhenVolumeFull() throws Exception { hddsDispatcher.setClusterId(scmId.toString()); containerData.getVolume().getVolumeInfo() .ifPresent(volumeInfo -> volumeInfo.incrementUsedSpace(50)); + usedSpace.addAndGet(50); ContainerCommandResponseProto response = hddsDispatcher .dispatch(getWriteChunkRequest(dd.getUuidString(), 1L, 1L), null); Assert.assertEquals(ContainerProtos.Result.SUCCESS, diff --git a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/report/TestReportPublisher.java b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/report/TestReportPublisher.java index 9fb9c7251cc4..42529cabc72a 100644 --- a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/report/TestReportPublisher.java +++ b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/report/TestReportPublisher.java @@ -173,8 +173,8 @@ public void testCommandStatusPublisher() throws InterruptedException { .build(); cmdStatusMap.put(obj1.getCmdId(), obj1); cmdStatusMap.put(obj2.getCmdId(), obj2); - // We are not sending the commands whose status is PENDING. - Assertions.assertEquals(1, + // We will sending the commands whose status is PENDING and EXECUTED + Assertions.assertEquals(2, ((CommandStatusReportPublisher) publisher).getReport() .getCmdStatusCount(), "Should publish report with 2 status objects"); diff --git a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/statemachine/commandhandler/TestClosePipelineCommandHandler.java b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/statemachine/commandhandler/TestClosePipelineCommandHandler.java new file mode 100644 index 000000000000..d161f5537ae9 --- /dev/null +++ b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/statemachine/commandhandler/TestClosePipelineCommandHandler.java @@ -0,0 +1,144 @@ +/* + * 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.ozone.container.common.statemachine.commandhandler; + +import com.google.common.util.concurrent.MoreExecutors; +import org.apache.hadoop.hdds.conf.OzoneConfiguration; +import org.apache.hadoop.hdds.protocol.DatanodeDetails; +import org.apache.hadoop.hdds.protocol.MockDatanodeDetails; +import org.apache.hadoop.hdds.ratis.RatisHelper; +import org.apache.hadoop.hdds.scm.pipeline.PipelineID; +import org.apache.hadoop.ozone.container.common.ContainerTestUtils; +import org.apache.hadoop.ozone.container.common.statemachine.SCMConnectionManager; +import org.apache.hadoop.ozone.container.common.statemachine.StateContext; +import org.apache.hadoop.ozone.container.common.transport.server.ratis.XceiverServerRatis; +import org.apache.hadoop.ozone.container.ozoneimpl.OzoneContainer; +import org.apache.hadoop.ozone.protocol.commands.ClosePipelineCommand; +import org.apache.hadoop.ozone.protocol.commands.SCMCommand; +import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.ClosePipelineCommandProto; +import org.apache.ratis.client.RaftClient; +import org.apache.ratis.client.api.GroupManagementApi; +import org.apache.ratis.protocol.RaftPeer; +import org.apache.ratis.protocol.RaftPeerId; +import org.apache.ratis.server.RaftServer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.BeforeEach; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Test cases to verify ClosePipelineCommandHandler. + */ +public class TestClosePipelineCommandHandler { + + private OzoneContainer ozoneContainer; + private StateContext stateContext; + private SCMConnectionManager connectionManager; + private RaftClient raftClient; + private GroupManagementApi raftClientGroupManager; + private OzoneConfiguration conf; + + @BeforeEach + public void setup() throws Exception { + conf = new OzoneConfiguration(); + ozoneContainer = mock(OzoneContainer.class); + connectionManager = mock(SCMConnectionManager.class); + raftClient = mock(RaftClient.class); + raftClientGroupManager = mock(GroupManagementApi.class); + lenient().when(raftClient.getGroupManagementApi( + any(RaftPeerId.class))).thenReturn(raftClientGroupManager); + } + + @Test + void testPipelineClose() throws IOException { + final List datanodes = getDatanodes(); + final DatanodeDetails currentDatanode = datanodes.get(0); + final PipelineID pipelineID = PipelineID.randomId(); + final SCMCommand command = + new ClosePipelineCommand(pipelineID); + stateContext = ContainerTestUtils.getMockContext(currentDatanode, conf); + + final boolean shouldDeleteRatisLogDirectory = true; + XceiverServerRatis writeChannel = mock(XceiverServerRatis.class); + when(ozoneContainer.getWriteChannel()).thenReturn(writeChannel); + when(writeChannel.getShouldDeleteRatisLogDirectory()).thenReturn(shouldDeleteRatisLogDirectory); + when(writeChannel.isExist(pipelineID.getProtobuf())).thenReturn(true); + Collection raftPeers = datanodes.stream() + .map(RatisHelper::toRaftPeer) + .collect(Collectors.toList()); + when(writeChannel.getServer()).thenReturn(mock(RaftServer.class)); + when(writeChannel.getServer().getId()).thenReturn(RatisHelper.toRaftPeerId(currentDatanode)); + when(writeChannel.getRaftPeersInPipeline(pipelineID)).thenReturn(raftPeers); + + final ClosePipelineCommandHandler commandHandler = + new ClosePipelineCommandHandler((leader, tls) -> raftClient, MoreExecutors.directExecutor()); + commandHandler.handle(command, ozoneContainer, stateContext, connectionManager); + + verify(writeChannel, times(1)) + .removeGroup(pipelineID.getProtobuf()); + + verify(raftClientGroupManager, times(2)) + .remove(any(), eq(shouldDeleteRatisLogDirectory), eq(!shouldDeleteRatisLogDirectory)); + } + + @Test + void testCommandIdempotency() throws IOException { + final List datanodes = getDatanodes(); + final DatanodeDetails currentDatanode = datanodes.get(0); + final PipelineID pipelineID = PipelineID.randomId(); + final SCMCommand command = + new ClosePipelineCommand(pipelineID); + stateContext = ContainerTestUtils.getMockContext(currentDatanode, conf); + + XceiverServerRatis writeChannel = mock(XceiverServerRatis.class); + when(ozoneContainer.getWriteChannel()).thenReturn(writeChannel); + // When the pipeline has been closed earlier by other datanode that received a close pipeline command + when(writeChannel.isExist(pipelineID.getProtobuf())).thenReturn(false); + + final ClosePipelineCommandHandler commandHandler = + new ClosePipelineCommandHandler(conf, MoreExecutors.directExecutor()); + commandHandler.handle(command, ozoneContainer, stateContext, connectionManager); + + verify(writeChannel, times(0)) + .removeGroup(pipelineID.getProtobuf()); + + verify(raftClientGroupManager, times(0)) + .remove(any(), anyBoolean(), anyBoolean()); + } + + private List getDatanodes() { + final DatanodeDetails dnOne = MockDatanodeDetails.randomDatanodeDetails(); + final DatanodeDetails dnTwo = MockDatanodeDetails.randomDatanodeDetails(); + final DatanodeDetails dnThree = MockDatanodeDetails.randomDatanodeDetails(); + return Arrays.asList(dnOne, dnTwo, dnThree); + } + +} diff --git a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/volume/TestCapacityVolumeChoosingPolicy.java b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/volume/TestCapacityVolumeChoosingPolicy.java index ff9227588552..3d90a98a233d 100644 --- a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/volume/TestCapacityVolumeChoosingPolicy.java +++ b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/volume/TestCapacityVolumeChoosingPolicy.java @@ -39,6 +39,7 @@ import java.util.Map; import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_DATANODE_VOLUME_CHOOSING_POLICY; +import static org.apache.hadoop.hdds.scm.ScmConfigKeys.HDDS_DATANODE_DIR_DU_RESERVED_PERCENT; import static org.apache.ozone.test.GenericTestUtils.getTestDir; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -62,6 +63,9 @@ public class TestCapacityVolumeChoosingPolicy { @BeforeEach public void setup() throws Exception { policy = new CapacityVolumeChoosingPolicy(); + // Use the exact capacity and availability specified in this test. Do not reserve space to prevent volumes from + // filling up. + CONF.setFloat(HDDS_DATANODE_DIR_DU_RESERVED_PERCENT, 0); SpaceUsageSource source1 = MockSpaceUsageSource.fixed(500, 100); SpaceUsageCheckFactory factory1 = MockSpaceUsageCheckFactory.of( diff --git a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/volume/TestHddsVolume.java b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/volume/TestHddsVolume.java index 4c4e32b07bfb..b6a6d2566f3b 100644 --- a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/volume/TestHddsVolume.java +++ b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/volume/TestHddsVolume.java @@ -510,6 +510,7 @@ public void testFailedVolumeSpace() throws IOException { assertEquals(0, volumeInfoMetrics.getCapacity()); assertEquals(0, volumeInfoMetrics.getReserved()); assertEquals(0, volumeInfoMetrics.getTotalCapacity()); + assertEquals(0, volumeInfoMetrics.getCommitted()); } finally { // Shutdown the volume. volume.shutdown(); diff --git a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/volume/TestReservedVolumeSpace.java b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/volume/TestReservedVolumeSpace.java index 86b7689e0009..19fa52c63f3a 100644 --- a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/volume/TestReservedVolumeSpace.java +++ b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/volume/TestReservedVolumeSpace.java @@ -26,11 +26,14 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import java.io.File; +import java.nio.file.Files; import java.nio.file.Path; import java.util.UUID; import static org.apache.hadoop.hdds.scm.ScmConfigKeys.HDDS_DATANODE_DIR_DU_RESERVED_PERCENT; import static org.apache.hadoop.hdds.scm.ScmConfigKeys.HDDS_DATANODE_DIR_DU_RESERVED_PERCENT_DEFAULT; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * To test the reserved volume space. @@ -51,6 +54,27 @@ public void setup() throws Exception { .usageCheckFactory(MockSpaceUsageCheckFactory.NONE); } + @Test + public void testDefaultConfig() throws Exception { + OzoneConfiguration conf = new OzoneConfiguration(); + HddsVolume hddsVolume = volumeBuilder.conf(conf).build(); + float percentage = conf.getFloat(HDDS_DATANODE_DIR_DU_RESERVED_PERCENT, + HDDS_DATANODE_DIR_DU_RESERVED_PERCENT_DEFAULT); + assertEquals(percentage, HDDS_DATANODE_DIR_DU_RESERVED_PERCENT_DEFAULT); + + // Gets the total capacity reported by Ozone, which may be limited to less than the volume's real capacity by the + // DU reserved configurations. + long volumeCapacity = hddsVolume.getCapacity(); + VolumeUsage usage = hddsVolume.getVolumeInfo().get().getUsageForTesting(); + + // Gets the actual total capacity without accounting for DU reserved space configurations. + long totalCapacity = usage.realUsage().getCapacity(); + long reservedCapacity = usage.getReservedBytes(); + + assertEquals(getExpectedDefaultReserved(hddsVolume), reservedCapacity); + assertEquals(totalCapacity - reservedCapacity, volumeCapacity); + } + /** * Test reserved capacity with respect to the percentage of actual capacity. * @throws Exception @@ -65,20 +89,15 @@ public void testVolumeCapacityAfterReserve() throws Exception { HDDS_DATANODE_DIR_DU_RESERVED_PERCENT_DEFAULT); long volumeCapacity = hddsVolume.getCapacity(); - //Gets the actual total capacity - long totalCapacity = hddsVolume.getVolumeInfo().get() - .getUsageForTesting().getCapacity(); - long reservedCapacity = hddsVolume.getVolumeInfo().get() - .getReservedInBytes(); - //Volume Capacity with Reserved - long volumeCapacityReserved = totalCapacity - reservedCapacity; + VolumeUsage usage = hddsVolume.getVolumeInfo().get().getUsageForTesting(); - long reservedFromVolume = hddsVolume.getVolumeInfo().get() - .getReservedInBytes(); + //Gets the actual total capacity + long totalCapacity = usage.realUsage().getCapacity(); + long reservedCapacity = usage.getReservedBytes(); long reservedCalculated = (long) Math.ceil(totalCapacity * percentage); - Assertions.assertEquals(reservedFromVolume, reservedCalculated); - Assertions.assertEquals(volumeCapacity, volumeCapacityReserved); + Assertions.assertEquals(reservedCalculated, reservedCapacity); + Assertions.assertEquals(totalCapacity - reservedCapacity, volumeCapacity); } /** @@ -96,17 +115,7 @@ public void testReservedWhenBothConfigSet() throws Exception { long reservedFromVolume = hddsVolume.getVolumeInfo().get() .getReservedInBytes(); - Assertions.assertEquals(reservedFromVolume, 500); - } - - @Test - public void testReservedToZeroWhenBothConfigNotSet() throws Exception { - OzoneConfiguration conf = new OzoneConfiguration(); - HddsVolume hddsVolume = volumeBuilder.conf(conf).build(); - - long reservedFromVolume = hddsVolume.getVolumeInfo().get() - .getReservedInBytes(); - Assertions.assertEquals(reservedFromVolume, 0); + assertEquals(500, reservedFromVolume); } @Test @@ -118,16 +127,15 @@ public void testFallbackToPercentConfig() throws Exception { temp.toString() + ":500B"); HddsVolume hddsVolume = volumeBuilder.conf(conf).build(); - long reservedFromVolume = hddsVolume.getVolumeInfo().get() - .getReservedInBytes(); - Assertions.assertNotEquals(reservedFromVolume, 0); + VolumeUsage usage = hddsVolume.getVolumeInfo().get().getUsageForTesting(); + long reservedFromVolume = usage.getReservedBytes(); + Assertions.assertNotEquals(0, reservedFromVolume); - long totalCapacity = hddsVolume.getVolumeInfo().get() - .getUsageForTesting().getCapacity(); + long totalCapacity = usage.realUsage().getCapacity(); float percentage = conf.getFloat(HDDS_DATANODE_DIR_DU_RESERVED_PERCENT, HDDS_DATANODE_DIR_DU_RESERVED_PERCENT_DEFAULT); long reservedCalculated = (long) Math.ceil(totalCapacity * percentage); - Assertions.assertEquals(reservedFromVolume, reservedCalculated); + Assertions.assertEquals(reservedCalculated, reservedFromVolume); } @Test @@ -141,7 +149,7 @@ public void testInvalidConfig() throws Exception { long reservedFromVolume1 = hddsVolume1.getVolumeInfo().get() .getReservedInBytes(); - Assertions.assertEquals(reservedFromVolume1, 0); + Assertions.assertEquals(getExpectedDefaultReserved(hddsVolume1), reservedFromVolume1); OzoneConfiguration conf2 = new OzoneConfiguration(); @@ -151,6 +159,27 @@ public void testInvalidConfig() throws Exception { long reservedFromVolume2 = hddsVolume2.getVolumeInfo().get() .getReservedInBytes(); - Assertions.assertEquals(reservedFromVolume2, 0); + Assertions.assertEquals(getExpectedDefaultReserved(hddsVolume2), reservedFromVolume2); + } + + @Test + public void testPathsCanonicalized() throws Exception { + OzoneConfiguration conf = new OzoneConfiguration(); + + // Create symlink in folder (which is the root of the volume) + Path symlink = new File(temp.toFile(), "link").toPath(); + Files.createSymbolicLink(symlink, folder); + + // Use the symlink in the configuration. Canonicalization should still match it to folder used in the volume config. + conf.set(ScmConfigKeys.HDDS_DATANODE_DIR_DU_RESERVED, symlink + ":500B"); + HddsVolume hddsVolume = volumeBuilder.conf(conf).build(); + + long reservedFromVolume = hddsVolume.getVolumeInfo().get().getReservedInBytes(); + assertEquals(500, reservedFromVolume); + } + + private long getExpectedDefaultReserved(HddsVolume volume) { + long totalCapacity = volume.getVolumeInfo().get().getUsageForTesting().realUsage().getCapacity(); + return (long) Math.ceil(totalCapacity * HDDS_DATANODE_DIR_DU_RESERVED_PERCENT_DEFAULT); } } diff --git a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/volume/TestRoundRobinVolumeChoosingPolicy.java b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/volume/TestRoundRobinVolumeChoosingPolicy.java index 582f6a86a4b4..fb8b3c596b20 100644 --- a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/volume/TestRoundRobinVolumeChoosingPolicy.java +++ b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/volume/TestRoundRobinVolumeChoosingPolicy.java @@ -40,6 +40,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import static org.apache.hadoop.hdds.scm.ScmConfigKeys.HDDS_DATANODE_DIR_DU_RESERVED_PERCENT; + /** * Tests {@link RoundRobinVolumeChoosingPolicy}. */ @@ -59,6 +61,10 @@ public class TestRoundRobinVolumeChoosingPolicy { public void setup() throws Exception { policy = new RoundRobinVolumeChoosingPolicy(); + // Use the exact capacity and availability specified in this test. Do not reserve space to prevent volumes from + // filling up. + CONF.setFloat(HDDS_DATANODE_DIR_DU_RESERVED_PERCENT, 0); + SpaceUsageSource source1 = MockSpaceUsageSource.fixed(500, 100); SpaceUsageCheckFactory factory1 = MockSpaceUsageCheckFactory.of( source1, Duration.ZERO, SpaceUsagePersistence.None.INSTANCE); diff --git a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/keyvalue/TestKeyValueHandlerWithUnhealthyContainer.java b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/keyvalue/TestKeyValueHandlerWithUnhealthyContainer.java index 8fd8d08f24fd..7fd4cf3fcb8c 100644 --- a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/keyvalue/TestKeyValueHandlerWithUnhealthyContainer.java +++ b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/keyvalue/TestKeyValueHandlerWithUnhealthyContainer.java @@ -23,6 +23,7 @@ import org.apache.hadoop.hdds.protocol.DatanodeDetails; import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos; import org.apache.hadoop.hdds.scm.pipeline.MockPipeline; +import org.apache.hadoop.ozone.ClientVersion; import org.apache.hadoop.ozone.container.common.ContainerTestUtils; import org.apache.hadoop.ozone.container.common.helpers.ContainerMetrics; import org.apache.hadoop.ozone.container.common.impl.ContainerSet; @@ -35,11 +36,17 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; +import java.util.Arrays; import java.util.UUID; +import java.util.stream.IntStream; +import java.util.stream.Stream; import static org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.Result.CONTAINER_INTERNAL_ERROR; import static org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.Result.SUCCESS; @@ -92,6 +99,30 @@ public void testGetBlock() { assertEquals(UNKNOWN_BCSID, response.getResult()); } + private static Stream getAllClientVersions() { + return Arrays.stream(ClientVersion.values()).flatMap(client -> IntStream.range(0, 6) + .mapToObj(rid -> Arguments.of(client, rid))); + } + + + @ParameterizedTest + @MethodSource("getAllClientVersions") + public void testGetBlockWithReplicaIndexMismatch(ClientVersion clientVersion, int replicaIndex) { + KeyValueContainer container = getMockContainerWithReplicaIndex(replicaIndex); + KeyValueHandler handler = getDummyHandler(); + for (int rid = 0; rid <= 5; rid++) { + ContainerProtos.ContainerCommandResponseProto response = + handler.handleGetBlock( + getDummyCommandRequestProto(clientVersion, ContainerProtos.Type.GetBlock, rid), + container); + assertEquals((replicaIndex > 0 && rid != replicaIndex && clientVersion.toProtoValue() >= + ClientVersion.EC_REPLICA_INDEX_REQUIRED_IN_BLOCK_REQUEST.toProtoValue()) ? + ContainerProtos.Result.CONTAINER_NOT_FOUND : UNKNOWN_BCSID, + response.getResult()); + } + + } + @Test public void testGetCommittedBlockLength() { KeyValueContainer container = getMockUnhealthyContainer(); @@ -118,6 +149,23 @@ public void testReadChunk() { assertEquals(UNKNOWN_BCSID, response.getResult()); } + @ParameterizedTest + @MethodSource("getAllClientVersions") + public void testReadChunkWithReplicaIndexMismatch(ClientVersion clientVersion, int replicaIndex) { + KeyValueContainer container = getMockContainerWithReplicaIndex(replicaIndex); + KeyValueHandler handler = getDummyHandler(); + for (int rid = 0; rid <= 5; rid++) { + ContainerProtos.ContainerCommandResponseProto response = + handler.handleReadChunk(getDummyCommandRequestProto(clientVersion, ContainerProtos.Type.ReadChunk, rid), + container, null); + assertEquals((replicaIndex > 0 && rid != replicaIndex && + clientVersion.toProtoValue() >= ClientVersion.EC_REPLICA_INDEX_REQUIRED_IN_BLOCK_REQUEST.toProtoValue()) ? + ContainerProtos.Result.CONTAINER_NOT_FOUND : UNKNOWN_BCSID, + response.getResult()); + } + + } + @Test public void testGetSmallFile() { KeyValueContainer container = getMockUnhealthyContainer(); @@ -201,4 +249,15 @@ private KeyValueContainer getMockUnhealthyContainer() { .ContainerDataProto.newBuilder().setContainerID(1).build()); return new KeyValueContainer(containerData, new OzoneConfiguration()); } + + private KeyValueContainer getMockContainerWithReplicaIndex(int replicaIndex) { + KeyValueContainerData containerData = mock(KeyValueContainerData.class); + when(containerData.getState()).thenReturn( + ContainerProtos.ContainerDataProto.State.CLOSED); + when(containerData.getBlockCommitSequenceId()).thenReturn(100L); + when(containerData.getReplicaIndex()).thenReturn(replicaIndex); + when(containerData.getProtoBufMessage()).thenReturn(ContainerProtos + .ContainerDataProto.newBuilder().setContainerID(1).build()); + return new KeyValueContainer(containerData, new OzoneConfiguration()); + } } diff --git a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/keyvalue/impl/TestKeyValueStreamDataChannel.java b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/keyvalue/impl/TestKeyValueStreamDataChannel.java index 2da3486847a3..6025f661be2e 100644 --- a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/keyvalue/impl/TestKeyValueStreamDataChannel.java +++ b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/keyvalue/impl/TestKeyValueStreamDataChannel.java @@ -24,6 +24,7 @@ import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.PutBlockRequestProto; import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.Type; import org.apache.hadoop.hdds.ratis.ContainerCommandRequestMessage; +import org.apache.hadoop.ozone.ClientVersion; import org.apache.hadoop.ozone.container.keyvalue.impl.KeyValueStreamDataChannel.Buffers; import org.apache.hadoop.ozone.container.keyvalue.impl.KeyValueStreamDataChannel.WriteMethod; import org.apache.ratis.client.api.DataStreamOutput; @@ -68,7 +69,7 @@ public class TestKeyValueStreamDataChannel { public static final Logger LOG = LoggerFactory.getLogger(TestKeyValueStreamDataChannel.class); - static final ContainerCommandRequestProto PUT_BLOCK_PROTO + private static final ContainerCommandRequestProto PUT_BLOCK_PROTO = ContainerCommandRequestProto.newBuilder() .setCmdType(Type.PutBlock) .setPutBlock(PutBlockRequestProto.newBuilder().setBlockData( @@ -76,6 +77,7 @@ public class TestKeyValueStreamDataChannel { .setContainerID(222).setLocalID(333).build()).build())) .setDatanodeUuid("datanodeId") .setContainerID(111L) + .setVersion(ClientVersion.CURRENT.toProtoValue()) .build(); static final int PUT_BLOCK_PROTO_SIZE = PUT_BLOCK_PROTO.toByteString().size(); static { diff --git a/hadoop-hdds/docs/themes/ozonedoc/static/swagger-resources/recon-api.yaml b/hadoop-hdds/docs/themes/ozonedoc/static/swagger-resources/recon-api.yaml index 3b41132f5f57..9ff328776657 100644 --- a/hadoop-hdds/docs/themes/ozonedoc/static/swagger-resources/recon-api.yaml +++ b/hadoop-hdds/docs/themes/ozonedoc/static/swagger-resources/recon-api.yaml @@ -1433,6 +1433,9 @@ components: remaining: type: number example: 1080410456064 + committed: + type: number + example: 1080410456 containers: type: integer example: 26 @@ -1480,6 +1483,9 @@ components: remaining: type: number example: 270071111680 + committed: + type: number + example: 27007111 pipelines: type: array items: diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/db/RDBStore.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/db/RDBStore.java index 3feff9314b7c..f47af5da52b0 100644 --- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/db/RDBStore.java +++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/db/RDBStore.java @@ -164,7 +164,7 @@ public RDBStore(File dbFile, ManagedDBOptions dbOptions, ManagedStatistics stati rocksDBCheckpointDiffer.setCompactionLogTableCFHandle( compactionLogTableCF.getHandle()); // Set activeRocksDB in differ to access compaction log CF. - rocksDBCheckpointDiffer.setActiveRocksDB(db.getManagedRocksDb().get()); + rocksDBCheckpointDiffer.setActiveRocksDB(db.getManagedRocksDb()); // Load all previous compaction logs rocksDBCheckpointDiffer.loadAllCompactionLogs(); } diff --git a/hadoop-hdds/hadoop-dependency-server/pom.xml b/hadoop-hdds/hadoop-dependency-server/pom.xml index 1a014b467f8e..559e7d0d9f27 100644 --- a/hadoop-hdds/hadoop-dependency-server/pom.xml +++ b/hadoop-hdds/hadoop-dependency-server/pom.xml @@ -154,6 +154,14 @@ https://maven.apache.org/xsd/maven-4.0.0.xsd"> + + org.apache.kerby + kerb-core + + + org.apache.kerby + kerb-util + org.xerial.snappy snappy-java diff --git a/hadoop-hdds/interface-client/src/main/proto/hdds.proto b/hadoop-hdds/interface-client/src/main/proto/hdds.proto index 5c20745c061e..3f346300b3ed 100644 --- a/hadoop-hdds/interface-client/src/main/proto/hdds.proto +++ b/hadoop-hdds/interface-client/src/main/proto/hdds.proto @@ -187,6 +187,8 @@ message DatanodeUsageInfoProto { optional int64 remaining = 3; optional DatanodeDetailsProto node = 4; optional int64 containerCount = 5; + optional int64 committed = 6; + optional int64 freeSpaceToSpare = 7; } /** diff --git a/hadoop-hdds/interface-server/src/main/proto/ScmServerDatanodeHeartbeatProtocol.proto b/hadoop-hdds/interface-server/src/main/proto/ScmServerDatanodeHeartbeatProtocol.proto index de9e39789b51..2994073c0240 100644 --- a/hadoop-hdds/interface-server/src/main/proto/ScmServerDatanodeHeartbeatProtocol.proto +++ b/hadoop-hdds/interface-server/src/main/proto/ScmServerDatanodeHeartbeatProtocol.proto @@ -179,6 +179,8 @@ message StorageReportProto { optional uint64 remaining = 5 [default = 0]; optional StorageTypeProto storageType = 6 [default = DISK]; optional bool failed = 7 [default = false]; + optional uint64 committed = 8 [default = 0]; + optional uint64 freeSpaceToSpare = 9 [default = 0]; } message MetadataStorageReportProto { diff --git a/hadoop-hdds/managed-rocksdb/src/main/java/org/apache/hadoop/hdds/utils/db/managed/ManagedSstFileReader.java b/hadoop-hdds/managed-rocksdb/src/main/java/org/apache/hadoop/hdds/utils/db/managed/ManagedSstFileReader.java index b49c6e7a9e49..38d09e601d26 100644 --- a/hadoop-hdds/managed-rocksdb/src/main/java/org/apache/hadoop/hdds/utils/db/managed/ManagedSstFileReader.java +++ b/hadoop-hdds/managed-rocksdb/src/main/java/org/apache/hadoop/hdds/utils/db/managed/ManagedSstFileReader.java @@ -18,18 +18,29 @@ */ package org.apache.hadoop.hdds.utils.db.managed; +import org.apache.ratis.util.UncheckedAutoCloseable; +import org.rocksdb.Options; import org.rocksdb.SstFileReader; +import static org.apache.hadoop.hdds.utils.db.managed.ManagedRocksObjectUtils.track; + /** * Managed SstFileReader. */ -public class ManagedSstFileReader extends ManagedObject { +public class ManagedSstFileReader extends SstFileReader { + + private final UncheckedAutoCloseable leakTracker = track(this); - ManagedSstFileReader(SstFileReader original) { - super(original); + public ManagedSstFileReader(final Options options) { + super(options); } - public static ManagedSstFileReader managed(SstFileReader reader) { - return new ManagedSstFileReader(reader); + @Override + public void close() { + try { + super.close(); + } finally { + leakTracker.close(); + } } } diff --git a/hadoop-hdds/rocksdb-checkpoint-differ/pom.xml b/hadoop-hdds/rocksdb-checkpoint-differ/pom.xml index ab6861c12f61..9284a9aef077 100644 --- a/hadoop-hdds/rocksdb-checkpoint-differ/pom.xml +++ b/hadoop-hdds/rocksdb-checkpoint-differ/pom.xml @@ -157,43 +157,6 @@ https://maven.apache.org/xsd/maven-4.0.0.xsd"> depcheck - - banned-rocksdb-imports - process-sources - - enforce - - - - - false - Use managed RocksObjects under org.apache.hadoop.hdds.utils.db.managed instead. - - org.rocksdb.** - - org.rocksdb.AbstractEventListener - org.rocksdb.Checkpoint - org.rocksdb.ColumnFamilyDescriptor - org.rocksdb.ColumnFamilyHandle - org.rocksdb.ColumnFamilyOptions - org.rocksdb.CompactionJobInfo - org.rocksdb.CompressionType - org.rocksdb.DBOptions - org.rocksdb.FlushOptions - org.rocksdb.LiveFileMetaData - org.rocksdb.Options - org.rocksdb.RocksDB - org.rocksdb.RocksDBException - org.rocksdb.SstFileReader - org.rocksdb.TableProperties - org.rocksdb.ReadOptions - org.rocksdb.SstFileReaderIterator - - org.apache.hadoop.hdds.utils.db.managed.* - - - - diff --git a/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdb/util/ManagedSstFileReader.java b/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdb/util/SstFileSetReader.java similarity index 87% rename from hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdb/util/ManagedSstFileReader.java rename to hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdb/util/SstFileSetReader.java index 7d25413072b6..5a1df61b4436 100644 --- a/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdb/util/ManagedSstFileReader.java +++ b/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdb/util/SstFileSetReader.java @@ -21,15 +21,14 @@ import org.apache.hadoop.hdds.StringUtils; import org.apache.hadoop.hdds.utils.IOUtils; import org.apache.hadoop.hdds.utils.db.managed.ManagedSlice; +import org.apache.hadoop.hdds.utils.db.managed.ManagedSstFileReader; +import org.apache.hadoop.hdds.utils.db.managed.ManagedSstFileReaderIterator; import org.apache.hadoop.util.ClosableIterator; import org.apache.hadoop.hdds.utils.db.managed.ManagedOptions; import org.apache.hadoop.hdds.utils.db.managed.ManagedReadOptions; import org.apache.hadoop.hdds.utils.db.managed.ManagedSSTDumpIterator; import org.apache.hadoop.hdds.utils.db.managed.ManagedSSTDumpTool; -import org.rocksdb.ReadOptions; import org.rocksdb.RocksDBException; -import org.rocksdb.SstFileReader; -import org.rocksdb.SstFileReaderIterator; import java.io.IOException; import java.io.UncheckedIOException; @@ -46,16 +45,16 @@ import static java.nio.charset.StandardCharsets.UTF_8; /** - * ManagedSstFileReader provides an abstraction layer using which we can - * iterate over multiple underlying SST files transparently. + * Provides an abstraction layer using which we can iterate over multiple + * underlying SST files transparently. */ -public class ManagedSstFileReader { +public class SstFileSetReader { private final Collection sstFiles; private volatile long estimatedTotalKeys = -1; - public ManagedSstFileReader(final Collection sstFiles) { + public SstFileSetReader(final Collection sstFiles) { this.sstFiles = sstFiles; } @@ -78,7 +77,7 @@ public long getEstimatedTotalKeys() throws RocksDBException { try (ManagedOptions options = new ManagedOptions()) { for (String sstFile : sstFiles) { - try (SstFileReader fileReader = new SstFileReader(options)) { + try (ManagedSstFileReader fileReader = new ManagedSstFileReader(options)) { fileReader.open(sstFile); estimatedSize += fileReader.getTableProperties().getNumEntries(); } @@ -96,7 +95,7 @@ public Stream getKeyStream(String lowerBound, final MultipleSstFileIterator itr = new MultipleSstFileIterator(sstFiles) { private ManagedOptions options; - private ReadOptions readOptions; + private ManagedReadOptions readOptions; private ManagedSlice lowerBoundSLice; @@ -125,8 +124,8 @@ protected ClosableIterator getKeyIteratorForFile(String file) return new ManagedSstFileIterator(file, options, readOptions) { @Override protected String getIteratorValue( - SstFileReaderIterator iterator) { - return new String(iterator.key(), UTF_8); + ManagedSstFileReaderIterator iterator) { + return new String(iterator.get().key(), UTF_8); } }; } @@ -188,18 +187,17 @@ public void close() throws UncheckedIOException { return getStreamFromIterator(itr); } - private abstract static class ManagedSstFileIterator implements - ClosableIterator { - private SstFileReader fileReader; - private SstFileReaderIterator fileReaderIterator; + private abstract static class ManagedSstFileIterator implements ClosableIterator { + private final ManagedSstFileReader fileReader; + private final ManagedSstFileReaderIterator fileReaderIterator; ManagedSstFileIterator(String path, ManagedOptions options, - ReadOptions readOptions) + ManagedReadOptions readOptions) throws RocksDBException { - this.fileReader = new SstFileReader(options); + this.fileReader = new ManagedSstFileReader(options); this.fileReader.open(path); - this.fileReaderIterator = fileReader.newIterator(readOptions); - fileReaderIterator.seekToFirst(); + this.fileReaderIterator = ManagedSstFileReaderIterator.managed(fileReader.newIterator(readOptions)); + fileReaderIterator.get().seekToFirst(); } @Override @@ -210,15 +208,15 @@ public void close() { @Override public boolean hasNext() { - return fileReaderIterator.isValid(); + return fileReaderIterator.get().isValid(); } - protected abstract String getIteratorValue(SstFileReaderIterator iterator); + protected abstract String getIteratorValue(ManagedSstFileReaderIterator iterator); @Override public String next() { String value = getIteratorValue(fileReaderIterator); - fileReaderIterator.next(); + fileReaderIterator.get().next(); return value; } } diff --git a/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/RocksDBCheckpointDiffer.java b/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/RocksDBCheckpointDiffer.java index 5e612d8b204d..6dbdd0970053 100644 --- a/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/RocksDBCheckpointDiffer.java +++ b/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/RocksDBCheckpointDiffer.java @@ -41,10 +41,13 @@ import org.apache.hadoop.hdds.protocol.proto.HddsProtos.CompactionLogEntryProto; import org.apache.hadoop.hdds.utils.IOUtils; import org.apache.hadoop.hdds.utils.Scheduler; +import org.apache.hadoop.hdds.utils.db.managed.ManagedDBOptions; import org.apache.hadoop.hdds.utils.db.managed.ManagedOptions; import org.apache.hadoop.hdds.utils.db.managed.ManagedReadOptions; import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksIterator; import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksDB; +import org.apache.hadoop.hdds.utils.db.managed.ManagedSstFileReader; +import org.apache.hadoop.hdds.utils.db.managed.ManagedSstFileReaderIterator; import org.apache.ozone.compaction.log.CompactionFileInfo; import org.apache.ozone.compaction.log.CompactionLogEntry; import org.apache.ozone.rocksdb.util.RdbUtil; @@ -53,13 +56,9 @@ import org.rocksdb.AbstractEventListener; import org.rocksdb.ColumnFamilyHandle; import org.rocksdb.CompactionJobInfo; -import org.rocksdb.DBOptions; import org.rocksdb.LiveFileMetaData; -import org.rocksdb.Options; import org.rocksdb.RocksDB; import org.rocksdb.RocksDBException; -import org.rocksdb.SstFileReader; -import org.rocksdb.SstFileReaderIterator; import org.rocksdb.TableProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -176,7 +175,7 @@ public class RocksDBCheckpointDiffer implements AutoCloseable, private AtomicBoolean suspended; private ColumnFamilyHandle compactionLogTableCFHandle; - private RocksDB activeRocksDB; + private ManagedRocksDB activeRocksDB; /** * For snapshot diff calculation we only need to track following column @@ -301,7 +300,7 @@ private void createSstBackUpDir() { } @Override - public void close() throws Exception { + public void close() { if (!closed) { synchronized (this) { if (!closed) { @@ -348,32 +347,11 @@ public static void addDebugLevel(Integer level) { DEBUG_LEVEL.add(level); } - /** - * Takes {@link org.rocksdb.Options}. - */ - public void setRocksDBForCompactionTracking(Options rocksOptions, - List list) { - list.add(newCompactionBeginListener()); - list.add(newCompactionCompletedListener()); - rocksOptions.setListeners(list); - } - - public void setRocksDBForCompactionTracking(Options rocksOptions) { - setRocksDBForCompactionTracking(rocksOptions, new ArrayList<>()); - } - - /** - * Takes {@link org.rocksdb.DBOptions}. - */ - public void setRocksDBForCompactionTracking(DBOptions rocksOptions, - List list) { - list.add(newCompactionBeginListener()); - list.add(newCompactionCompletedListener()); - rocksOptions.setListeners(list); - } - - public void setRocksDBForCompactionTracking(DBOptions rocksOptions) { - setRocksDBForCompactionTracking(rocksOptions, new ArrayList<>()); + public void setRocksDBForCompactionTracking(ManagedDBOptions rocksOptions) { + List events = new ArrayList<>(); + events.add(newCompactionBeginListener()); + events.add(newCompactionCompletedListener()); + rocksOptions.setListeners(events); } /** @@ -402,7 +380,7 @@ public synchronized void setCompactionLogTableCFHandle( * Set activeRocksDB to access CompactionLogTable. * @param activeRocksDB RocksDB */ - public synchronized void setActiveRocksDB(RocksDB activeRocksDB) { + public synchronized void setActiveRocksDB(ManagedRocksDB activeRocksDB) { Preconditions.checkNotNull(activeRocksDB, "RocksDB should not be null."); this.activeRocksDB = activeRocksDB; } @@ -435,8 +413,7 @@ private boolean isSnapshotInfoTableEmpty(RocksDB db) { // Note the goal of compaction DAG is to track all compactions that happened // _after_ a DB checkpoint is taken. - try (ManagedRocksIterator it = ManagedRocksIterator.managed( - db.newIterator(snapshotInfoTableCFHandle))) { + try (ManagedRocksIterator it = ManagedRocksIterator.managed(db.newIterator(snapshotInfoTableCFHandle))) { it.get().seekToFirst(); return !it.get().isValid(); } @@ -498,7 +475,6 @@ public void onCompactionBegin(RocksDB db, }; } - private AbstractEventListener newCompactionCompletedListener() { return new AbstractEventListener() { @Override @@ -576,7 +552,7 @@ void addToCompactionLogTable(CompactionLogEntry compactionLogEntry) { byte[] key = keyString.getBytes(UTF_8); byte[] value = compactionLogEntry.getProtobuf().toByteArray(); try { - activeRocksDB.put(compactionLogTableCFHandle, key, value); + activeRocksDB.get().put(compactionLogTableCFHandle, key, value); } catch (RocksDBException exception) { // TODO: Revisit exception handling before merging the PR. throw new RuntimeException(exception); @@ -629,9 +605,8 @@ private long getSSTFileSummary(String filename) filename += SST_FILE_EXTENSION; } - try ( - ManagedOptions option = new ManagedOptions(); - SstFileReader reader = new SstFileReader(option)) { + try (ManagedOptions option = new ManagedOptions(); + ManagedSstFileReader reader = new ManagedSstFileReader(option)) { reader.open(getAbsoluteSstFilePath(filename)); @@ -801,7 +776,7 @@ public void loadAllCompactionLogs() { preconditionChecksForLoadAllCompactionLogs(); addEntriesFromLogFilesToDagAndCompactionLogTable(); try (ManagedRocksIterator managedRocksIterator = new ManagedRocksIterator( - activeRocksDB.newIterator(compactionLogTableCFHandle))) { + activeRocksDB.get().newIterator(compactionLogTableCFHandle))) { managedRocksIterator.get().seekToFirst(); while (managedRocksIterator.get().isValid()) { byte[] value = managedRocksIterator.get().value(); @@ -1252,7 +1227,7 @@ private synchronized Pair, List> getOlderFileNodes() { List keysToRemove = new ArrayList<>(); try (ManagedRocksIterator managedRocksIterator = new ManagedRocksIterator( - activeRocksDB.newIterator(compactionLogTableCFHandle))) { + activeRocksDB.get().newIterator(compactionLogTableCFHandle))) { managedRocksIterator.get().seekToFirst(); while (managedRocksIterator.get().isValid()) { CompactionLogEntry compactionLogEntry = CompactionLogEntry @@ -1282,7 +1257,7 @@ private synchronized void removeKeyFromCompactionLogTable( List keysToRemove) { try { for (byte[] key: keysToRemove) { - activeRocksDB.delete(compactionLogTableCFHandle, key); + activeRocksDB.get().delete(compactionLogTableCFHandle, key); } } catch (RocksDBException exception) { // TODO Handle exception properly before merging the PR. @@ -1575,18 +1550,19 @@ private CompactionFileInfo toFileInfo(String sstFile, CompactionFileInfo.Builder fileInfoBuilder = new CompactionFileInfo.Builder(fileName); - try (SstFileReader fileReader = new SstFileReader(options)) { + try (ManagedSstFileReader fileReader = new ManagedSstFileReader(options)) { fileReader.open(sstFile); - String columnFamily = StringUtils.bytes2String( - fileReader.getTableProperties().getColumnFamilyName()); - SstFileReaderIterator iterator = fileReader.newIterator(readOptions); - iterator.seekToFirst(); - String startKey = StringUtils.bytes2String(iterator.key()); - iterator.seekToLast(); - String endKey = StringUtils.bytes2String(iterator.key()); - fileInfoBuilder.setStartRange(startKey) - .setEndRange(endKey) - .setColumnFamily(columnFamily); + String columnFamily = StringUtils.bytes2String(fileReader.getTableProperties().getColumnFamilyName()); + try (ManagedSstFileReaderIterator iterator = + ManagedSstFileReaderIterator.managed(fileReader.newIterator(readOptions))) { + iterator.get().seekToFirst(); + String startKey = StringUtils.bytes2String(iterator.get().key()); + iterator.get().seekToLast(); + String endKey = StringUtils.bytes2String(iterator.get().key()); + fileInfoBuilder.setStartRange(startKey) + .setEndRange(endKey) + .setColumnFamily(columnFamily); + } } catch (RocksDBException rocksDBException) { // Ideally it should not happen. If it does just log the exception. // And let the compaction complete without the exception. diff --git a/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/RocksDiffUtils.java b/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/RocksDiffUtils.java index 5ddcf8b7e6af..e116868410f1 100644 --- a/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/RocksDiffUtils.java +++ b/hadoop-hdds/rocksdb-checkpoint-differ/src/main/java/org/apache/ozone/rocksdiff/RocksDiffUtils.java @@ -22,7 +22,6 @@ import org.apache.hadoop.hdds.utils.db.managed.ManagedReadOptions; import org.apache.hadoop.hdds.utils.db.managed.ManagedSstFileReader; import org.apache.hadoop.hdds.utils.db.managed.ManagedSstFileReaderIterator; -import org.rocksdb.SstFileReader; import org.rocksdb.TableProperties; import org.rocksdb.RocksDBException; import org.slf4j.Logger; @@ -90,9 +89,9 @@ public static boolean doesSstFileContainKeyRange(String filepath, try ( ManagedOptions options = new ManagedOptions(); - ManagedSstFileReader sstFileReader = ManagedSstFileReader.managed(new SstFileReader(options))) { - sstFileReader.get().open(filepath); - TableProperties properties = sstFileReader.get().getTableProperties(); + ManagedSstFileReader sstFileReader = new ManagedSstFileReader(options)) { + sstFileReader.open(filepath); + TableProperties properties = sstFileReader.getTableProperties(); String tableName = new String(properties.getColumnFamilyName(), UTF_8); if (tableToPrefixMap.containsKey(tableName)) { String prefix = tableToPrefixMap.get(tableName); @@ -100,7 +99,7 @@ public static boolean doesSstFileContainKeyRange(String filepath, try ( ManagedReadOptions readOptions = new ManagedReadOptions(); ManagedSstFileReaderIterator iterator = ManagedSstFileReaderIterator.managed( - sstFileReader.get().newIterator(readOptions))) { + sstFileReader.newIterator(readOptions))) { iterator.get().seek(prefix.getBytes(UTF_8)); String seekResultKey = new String(iterator.get().key(), UTF_8); return seekResultKey.startsWith(prefix); diff --git a/hadoop-hdds/rocksdb-checkpoint-differ/src/test/java/org/apache/ozone/rocksdb/util/TestManagedSstFileReader.java b/hadoop-hdds/rocksdb-checkpoint-differ/src/test/java/org/apache/ozone/rocksdb/util/TestSstFileSetReader.java similarity index 98% rename from hadoop-hdds/rocksdb-checkpoint-differ/src/test/java/org/apache/ozone/rocksdb/util/TestManagedSstFileReader.java rename to hadoop-hdds/rocksdb-checkpoint-differ/src/test/java/org/apache/ozone/rocksdb/util/TestSstFileSetReader.java index 8c897b01d2e4..676a3e35c04c 100644 --- a/hadoop-hdds/rocksdb-checkpoint-differ/src/test/java/org/apache/ozone/rocksdb/util/TestManagedSstFileReader.java +++ b/hadoop-hdds/rocksdb-checkpoint-differ/src/test/java/org/apache/ozone/rocksdb/util/TestSstFileSetReader.java @@ -56,7 +56,7 @@ /** * ManagedSstFileReader tests. */ -class TestManagedSstFileReader { +class TestSstFileSetReader { // Key prefix containing all characters, to check if all characters can be // written & read from rocksdb through SSTDumptool @@ -139,7 +139,7 @@ public void testGetKeyStream(int numberOfFiles) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); try (Stream keyStream = - new ManagedSstFileReader(files).getKeyStream( + new SstFileSetReader(files).getKeyStream( lowerBound.orElse(null), upperBound.orElse(null))) { keyStream.forEach(key -> { Assertions.assertEquals(keysInBoundary.get(key), 1); @@ -187,7 +187,7 @@ public void testGetKeyStreamWithTombstone(int numberOfFiles) .orElse(true)) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - try (Stream keyStream = new ManagedSstFileReader(files) + try (Stream keyStream = new SstFileSetReader(files) .getKeyStreamWithTombstone(sstDumpTool, lowerBound.orElse(null), upperBound.orElse(null))) { keyStream.forEach( diff --git a/hadoop-hdds/rocksdb-checkpoint-differ/src/test/java/org/apache/ozone/rocksdiff/TestRocksDBCheckpointDiffer.java b/hadoop-hdds/rocksdb-checkpoint-differ/src/test/java/org/apache/ozone/rocksdiff/TestRocksDBCheckpointDiffer.java index 7ce79745819c..b843e574d8db 100644 --- a/hadoop-hdds/rocksdb-checkpoint-differ/src/test/java/org/apache/ozone/rocksdiff/TestRocksDBCheckpointDiffer.java +++ b/hadoop-hdds/rocksdb-checkpoint-differ/src/test/java/org/apache/ozone/rocksdiff/TestRocksDBCheckpointDiffer.java @@ -56,9 +56,14 @@ import org.apache.hadoop.hdds.StringUtils; import org.apache.hadoop.hdds.conf.ConfigurationSource; import org.apache.hadoop.hdds.utils.IOUtils; +import org.apache.hadoop.hdds.utils.db.managed.ManagedCheckpoint; +import org.apache.hadoop.hdds.utils.db.managed.ManagedColumnFamilyOptions; +import org.apache.hadoop.hdds.utils.db.managed.ManagedDBOptions; +import org.apache.hadoop.hdds.utils.db.managed.ManagedFlushOptions; import org.apache.hadoop.hdds.utils.db.managed.ManagedOptions; import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksDB; import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksIterator; +import org.apache.hadoop.hdds.utils.db.managed.ManagedSstFileReader; import org.apache.hadoop.ozone.lock.BootstrapStateHandler; import org.apache.ozone.compaction.log.CompactionFileInfo; import org.apache.ozone.compaction.log.CompactionLogEntry; @@ -72,18 +77,11 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.mockito.Mockito; -import org.rocksdb.Checkpoint; import org.rocksdb.ColumnFamilyDescriptor; import org.rocksdb.ColumnFamilyHandle; -import org.rocksdb.ColumnFamilyOptions; -import org.rocksdb.DBOptions; -import org.rocksdb.FlushOptions; import org.rocksdb.LiveFileMetaData; -import org.rocksdb.Options; import org.rocksdb.RocksDB; import org.rocksdb.RocksDBException; -import org.rocksdb.RocksIterator; -import org.rocksdb.SstFileReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.event.Level; @@ -134,7 +132,9 @@ public class TestRocksDBCheckpointDiffer { private ConfigurationSource config; private ExecutorService executorService = Executors.newCachedThreadPool(); private RocksDBCheckpointDiffer rocksDBCheckpointDiffer; - private RocksDB activeRocksDB; + private ManagedRocksDB activeRocksDB; + + private ManagedDBOptions dbOptions; private ColumnFamilyHandle keyTableCFHandle; private ColumnFamilyHandle directoryTableCFHandle; private ColumnFamilyHandle fileTableCFHandle; @@ -177,17 +177,16 @@ public void init() throws RocksDBException { activeDbDirName, config); - ColumnFamilyOptions cfOpts = new ColumnFamilyOptions() - .optimizeUniversalStyleCompaction(); + ManagedColumnFamilyOptions cfOpts = new ManagedColumnFamilyOptions(); + cfOpts.optimizeUniversalStyleCompaction(); List cfDescriptors = getCFDescriptorList(cfOpts); List cfHandles = new ArrayList<>(); - DBOptions dbOptions = new DBOptions() - .setCreateIfMissing(true) - .setCreateMissingColumnFamilies(true); + dbOptions = new ManagedDBOptions(); + dbOptions.setCreateIfMissing(true); + dbOptions.setCreateMissingColumnFamilies(true); rocksDBCheckpointDiffer.setRocksDBForCompactionTracking(dbOptions); - activeRocksDB = RocksDB.open(dbOptions, activeDbDirName, cfDescriptors, - cfHandles); + activeRocksDB = ManagedRocksDB.open(dbOptions, activeDbDirName, cfDescriptors, cfHandles); keyTableCFHandle = cfHandles.get(1); directoryTableCFHandle = cfHandles.get(2); fileTableCFHandle = cfHandles.get(3); @@ -592,12 +591,12 @@ void diffAllSnapshots(RocksDBCheckpointDiffer differ) /** * Helper function that creates an RDB checkpoint (= Ozone snapshot). */ - private void createCheckpoint(RocksDB rocksDB) throws RocksDBException { + private void createCheckpoint(ManagedRocksDB rocksDB) throws RocksDBException { LOG.trace("Current time: " + System.currentTimeMillis()); long t1 = System.currentTimeMillis(); - final long snapshotGeneration = rocksDB.getLatestSequenceNumber(); + final long snapshotGeneration = rocksDB.get().getLatestSequenceNumber(); final String cpPath = CP_PATH_PREFIX + snapshotGeneration; // Delete the checkpoint dir if it already exists for the test @@ -622,12 +621,12 @@ private void createCheckpoint(RocksDB rocksDB) throws RocksDBException { } // Flushes the WAL and Creates a RocksDB checkpoint - void createCheckPoint(String dbPathArg, String cpPathArg, RocksDB rocksDB) { + void createCheckPoint(String dbPathArg, String cpPathArg, ManagedRocksDB rocksDB) { LOG.debug("Creating RocksDB '{}' checkpoint at '{}'", dbPathArg, cpPathArg); - try { - rocksDB.flush(new FlushOptions()); - Checkpoint cp = Checkpoint.create(rocksDB); - cp.createCheckpoint(cpPathArg); + try (ManagedFlushOptions flushOptions = new ManagedFlushOptions()) { + rocksDB.get().flush(flushOptions); + ManagedCheckpoint cp = ManagedCheckpoint.create(rocksDB); + cp.get().createCheckpoint(cpPathArg); } catch (RocksDBException e) { throw new RuntimeException(e.getMessage()); } @@ -645,7 +644,7 @@ void printAllSnapshots() { * @return List of ColumnFamilyDescriptor */ static List getCFDescriptorList( - ColumnFamilyOptions cfOpts) { + ManagedColumnFamilyOptions cfOpts) { return asList( new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpts), new ColumnFamilyDescriptor("keyTable".getBytes(UTF_8), cfOpts), @@ -662,7 +661,7 @@ private void writeKeysAndCheckpointing() throws RocksDBException { String valueStr = "Val-" + i + "-" + generatedString; byte[] key = keyStr.getBytes(UTF_8); // Put entry in keyTable - activeRocksDB.put(keyTableCFHandle, key, valueStr.getBytes(UTF_8)); + activeRocksDB.get().put(keyTableCFHandle, key, valueStr.getBytes(UTF_8)); if (i % SNAPSHOT_EVERY_SO_MANY_KEYS == 0) { createCheckpoint(activeRocksDB); } @@ -682,27 +681,32 @@ private boolean deleteDirectory(File directoryToBeDeleted) { return directoryToBeDeleted.delete(); } + public List getColumnFamilyDescriptors(String dbPath) throws RocksDBException { + try (ManagedOptions emptyOptions = new ManagedOptions()) { + List cfList = RocksDB.listColumnFamilies(emptyOptions, dbPath); + return cfList.stream().map(ColumnFamilyDescriptor::new).collect(Collectors.toList()); + } + } + // Read from a given RocksDB instance and optionally write all the // keys to a given file. private void readRocksDBInstance(String dbPathArg, - RocksDB rocksDB, + ManagedRocksDB rocksDB, FileWriter file, RocksDBCheckpointDiffer differ) { LOG.debug("Reading RocksDB: " + dbPathArg); boolean createdDB = false; - try (Options options = new Options() - .setParanoidChecks(true) - .setForceConsistencyChecks(false)) { - + try (ManagedDBOptions dbOptions = new ManagedDBOptions()) { + List cfDescriptors = getColumnFamilyDescriptors(dbPathArg); + List cfHandles = new ArrayList<>(); if (rocksDB == null) { - rocksDB = RocksDB.openReadOnly(options, dbPathArg); + rocksDB = ManagedRocksDB.openReadOnly(dbOptions, dbPathArg, cfDescriptors, cfHandles); createdDB = true; } - List liveFileMetaDataList = - rocksDB.getLiveFilesMetaData(); + List liveFileMetaDataList = rocksDB.get().getLiveFilesMetaData(); for (LiveFileMetaData m : liveFileMetaDataList) { LOG.debug("SST File: {}. ", m.fileName()); LOG.debug("\tLevel: {}", m.level()); @@ -718,19 +722,21 @@ private void readRocksDBInstance(String dbPathArg, } if (differ.debugEnabled(DEBUG_READ_ALL_DB_KEYS)) { - RocksIterator iter = rocksDB.newIterator(); - for (iter.seekToFirst(); iter.isValid(); iter.next()) { - LOG.debug("Iterator key:" + toStr(iter.key()) + ", " + - "iter value:" + toStr(iter.value())); - if (file != null) { - file.write("iterator key:" + toStr(iter.key()) + ", iter " + - "value:" + toStr(iter.value())); - file.write("\n"); + try (ManagedRocksIterator iter = new ManagedRocksIterator(rocksDB.get().newIterator())) { + for (iter.get().seekToFirst(); iter.get().isValid(); iter.get().next()) { + LOG.debug( + "Iterator key:" + toStr(iter.get().key()) + ", " + + "iter value:" + toStr(iter.get().value())); + if (file != null) { + file.write("iterator key:" + toStr(iter.get().key()) + ", iter " + + "value:" + toStr(iter.get().value())); + file.write("\n"); + } } } } } catch (IOException | RocksDBException e) { - e.printStackTrace(); + LOG.error("Caught exception while reading from rocksDB.", e); } finally { if (createdDB) { rocksDB.close(); @@ -1314,7 +1320,7 @@ public void testPruneOlderSnapshotsWithCompactionHistory( private int countEntriesInCompactionLogTable() { try (ManagedRocksIterator iterator = new ManagedRocksIterator( - activeRocksDB.newIterator(compactionLogTableCFHandle))) { + activeRocksDB.get().newIterator(compactionLogTableCFHandle))) { iterator.get().seekToFirst(); int count = 0; while (iterator.get().isValid()) { @@ -1834,14 +1840,16 @@ private void createKeys(ColumnFamilyHandle cfh, String valuePrefix, int numberOfKeys) throws RocksDBException { - for (int i = 0; i < numberOfKeys; ++i) { - String generatedString = RandomStringUtils.randomAlphabetic(7); - String keyStr = keyPrefix + i + "-" + generatedString; - String valueStr = valuePrefix + i + "-" + generatedString; - byte[] key = keyStr.getBytes(UTF_8); - activeRocksDB.put(cfh, key, valueStr.getBytes(UTF_8)); - if (i % 10 == 0) { - activeRocksDB.flush(new FlushOptions(), cfh); + try (ManagedFlushOptions flushOptions = new ManagedFlushOptions()) { + for (int i = 0; i < numberOfKeys; ++i) { + String generatedString = RandomStringUtils.randomAlphabetic(7); + String keyStr = keyPrefix + i + "-" + generatedString; + String valueStr = valuePrefix + i + "-" + generatedString; + byte[] key = keyStr.getBytes(UTF_8); + activeRocksDB.get().put(cfh, key, valueStr.getBytes(UTF_8)); + if (i % 10 == 0) { + activeRocksDB.get().flush(flushOptions, cfh); + } } } } @@ -1878,7 +1886,7 @@ public void testDagOnlyContainsDesiredCfh() Stream pathStream = Files.list( Paths.get(rocksDBCheckpointDiffer.getSSTBackupDir()))) { pathStream.forEach(path -> { - try (SstFileReader fileReader = new SstFileReader(options)) { + try (ManagedSstFileReader fileReader = new ManagedSstFileReader(options)) { fileReader.open(path.toAbsolutePath().toString()); String columnFamily = StringUtils.bytes2String( fileReader.getTableProperties().getColumnFamilyName()); diff --git a/hadoop-hdds/server-scm/pom.xml b/hadoop-hdds/server-scm/pom.xml index fc0ec55b46b9..37e15afa52c3 100644 --- a/hadoop-hdds/server-scm/pom.xml +++ b/hadoop-hdds/server-scm/pom.xml @@ -163,6 +163,10 @@ https://maven.apache.org/xsd/maven-4.0.0.xsd"> junit-jupiter-params test + + org.mockito + mockito-junit-jupiter + diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/SCMCommonPlacementPolicy.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/SCMCommonPlacementPolicy.java index 0942b27898d4..471a94794122 100644 --- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/SCMCommonPlacementPolicy.java +++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/SCMCommonPlacementPolicy.java @@ -34,6 +34,7 @@ import org.apache.hadoop.hdds.scm.node.DatanodeInfo; import org.apache.hadoop.hdds.scm.node.NodeManager; import org.apache.hadoop.hdds.scm.node.NodeStatus; +import org.apache.hadoop.ozone.container.common.volume.VolumeUsage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -274,7 +275,7 @@ public List filterNodesWithSpace(List nodes, int nodesRequired, long metadataSizeRequired, long dataSizeRequired) throws SCMException { List nodesWithSpace = nodes.stream().filter(d -> - hasEnoughSpace(d, metadataSizeRequired, dataSizeRequired)) + hasEnoughSpace(d, metadataSizeRequired, dataSizeRequired, conf)) .collect(Collectors.toList()); if (nodesWithSpace.size() < nodesRequired) { @@ -298,7 +299,9 @@ public List filterNodesWithSpace(List nodes, * @return true if we have enough space. */ public static boolean hasEnoughSpace(DatanodeDetails datanodeDetails, - long metadataSizeRequired, long dataSizeRequired) { + long metadataSizeRequired, + long dataSizeRequired, + ConfigurationSource conf) { Preconditions.checkArgument(datanodeDetails instanceof DatanodeInfo); boolean enoughForData = false; @@ -308,7 +311,9 @@ public static boolean hasEnoughSpace(DatanodeDetails datanodeDetails, if (dataSizeRequired > 0) { for (StorageReportProto reportProto : datanodeInfo.getStorageReports()) { - if (reportProto.getRemaining() > dataSizeRequired) { + if (VolumeUsage.hasVolumeEnoughSpace(reportProto.getRemaining(), + reportProto.getCommitted(), dataSizeRequired, + reportProto.getFreeSpaceToSpare())) { enoughForData = true; break; } @@ -504,7 +509,7 @@ public boolean isValidNode(DatanodeDetails datanodeDetails, NodeStatus nodeStatus = datanodeInfo.getNodeStatus(); if (nodeStatus.isNodeWritable() && (hasEnoughSpace(datanodeInfo, metadataSizeRequired, - dataSizeRequired))) { + dataSizeRequired, conf))) { LOG.debug("Datanode {} is chosen. Required metadata size is {} and " + "required data size is {} and NodeStatus is {}", datanodeDetails, metadataSizeRequired, dataSizeRequired, nodeStatus); diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/DeletedBlockLog.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/DeletedBlockLog.java index cb9cf603b15e..45d53c0ef2cd 100644 --- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/DeletedBlockLog.java +++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/DeletedBlockLog.java @@ -19,12 +19,10 @@ import java.util.Set; import org.apache.hadoop.hdds.protocol.DatanodeDetails; -import org.apache.hadoop.hdds.protocol.proto - .StorageContainerDatanodeProtocolProtos.ContainerBlocksDeletionACKProto - .DeleteBlockTransactionResult; import org.apache.hadoop.hdds.protocol.proto .StorageContainerDatanodeProtocolProtos.DeletedBlocksTransaction; import org.apache.hadoop.hdds.utils.db.Table; +import org.apache.hadoop.ozone.protocol.commands.SCMCommand; import java.io.Closeable; import java.io.IOException; @@ -88,14 +86,33 @@ void incrementCount(List txIDs) int resetCount(List txIDs) throws IOException; /** - * Commits a transaction means to delete all footprints of a transaction - * from the log. This method doesn't guarantee all transactions can be - * successfully deleted, it tolerate failures and tries best efforts to. - * @param transactionResults - delete block transaction results. - * @param dnID - ID of datanode which acknowledges the delete block command. + * Records the creation of a transaction for a DataNode. + * + * @param dnId The identifier of the DataNode. + * @param scmCmdId The ID of the SCM command. + * @param dnTxSet Set of transaction IDs for the DataNode. + */ + void recordTransactionCreated( + UUID dnId, long scmCmdId, Set dnTxSet); + + /** + * Handles the cleanup process when a DataNode is reported dead. This method + * is responsible for updating or cleaning up the transaction records + * associated with the dead DataNode. + * + * @param dnId The identifier of the dead DataNode. + */ + void onDatanodeDead(UUID dnId); + + /** + * Records the event of sending a block deletion command to a DataNode. This + * method is called when a command is successfully dispatched to a DataNode, + * and it helps in tracking the status of the command. + * + * @param dnId Details of the DataNode. + * @param scmCommand The block deletion command sent. */ - void commitTransactions(List transactionResults, - UUID dnID); + void onSent(DatanodeDetails dnId, SCMCommand scmCommand); /** * Creates block deletion transactions for a set of containers, diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/DeletedBlockLogImpl.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/DeletedBlockLogImpl.java index 8e2d014916f6..ac64f6e973e3 100644 --- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/DeletedBlockLogImpl.java +++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/DeletedBlockLogImpl.java @@ -18,25 +18,25 @@ package org.apache.hadoop.hdds.scm.block; import java.io.IOException; +import java.time.Duration; import java.util.HashSet; import java.util.List; import java.util.UUID; import java.util.Set; import java.util.Map; -import java.util.LinkedHashSet; import java.util.ArrayList; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; +import com.google.common.annotations.VisibleForTesting; import org.apache.hadoop.hdds.conf.ConfigurationSource; import org.apache.hadoop.hdds.protocol.DatanodeDetails; +import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.CommandStatus; import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.ContainerBlocksDeletionACKProto; import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.ContainerBlocksDeletionACKProto.DeleteBlockTransactionResult; import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.DeletedBlocksTransaction; -import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.CommandStatus; import org.apache.hadoop.hdds.scm.command.CommandStatusReportHandler.DeleteBlockStatus; import org.apache.hadoop.hdds.scm.container.ContainerInfo; import org.apache.hadoop.hdds.scm.container.ContainerManager; @@ -55,11 +55,12 @@ import org.apache.hadoop.hdds.utils.db.TableIterator; import com.google.common.collect.Lists; -import static java.lang.Math.min; import static org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_BLOCK_DELETION_MAX_RETRY; import static org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_BLOCK_DELETION_MAX_RETRY_DEFAULT; +import static org.apache.hadoop.hdds.scm.block.SCMDeletedBlockTransactionStatusManager.SCMDeleteBlocksCommandStatusManager.CmdStatus; import static org.apache.hadoop.hdds.scm.ha.SequenceIdGenerator.DEL_TXN_ID; +import org.apache.hadoop.ozone.protocol.commands.SCMCommand; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -82,16 +83,15 @@ public class DeletedBlockLogImpl private final int maxRetry; private final ContainerManager containerManager; private final Lock lock; - // Maps txId to set of DNs which are successful in committing the transaction - private final Map> transactionToDNsCommitMap; - // Maps txId to its retry counts; - private final Map transactionToRetryCountMap; // The access to DeletedBlocksTXTable is protected by // DeletedBlockLogStateManager. private final DeletedBlockLogStateManager deletedBlockLogStateManager; private final SCMContext scmContext; private final SequenceIdGenerator sequenceIdGen; private final ScmBlockDeletingServiceMetrics metrics; + private final SCMDeletedBlockTransactionStatusManager + transactionStatusManager; + private long scmCommandTimeoutMs = Duration.ofSeconds(300).toMillis(); private static final int LIST_ALL_FAILED_TRANSACTIONS = -1; @@ -109,12 +109,6 @@ public DeletedBlockLogImpl(ConfigurationSource conf, this.containerManager = containerManager; this.lock = new ReentrantLock(); - // transactionToDNsCommitMap is updated only when - // transaction is added to the log and when it is removed. - - // maps transaction to dns which have committed it. - transactionToDNsCommitMap = new ConcurrentHashMap<>(); - transactionToRetryCountMap = new ConcurrentHashMap<>(); this.deletedBlockLogStateManager = DeletedBlockLogStateManagerImpl .newBuilder() .setConfiguration(conf) @@ -126,6 +120,9 @@ public DeletedBlockLogImpl(ConfigurationSource conf, this.scmContext = scmContext; this.sequenceIdGen = sequenceIdGen; this.metrics = metrics; + this.transactionStatusManager = + new SCMDeletedBlockTransactionStatusManager(deletedBlockLogStateManager, + containerManager, scmContext, metrics, scmCommandTimeoutMs); } @Override @@ -170,25 +167,7 @@ public void incrementCount(List txIDs) throws IOException { lock.lock(); try { - ArrayList txIDsToUpdate = new ArrayList<>(); - for (Long txID : txIDs) { - int currentCount = - transactionToRetryCountMap.getOrDefault(txID, 0); - if (currentCount > maxRetry) { - continue; - } else { - currentCount += 1; - if (currentCount > maxRetry) { - txIDsToUpdate.add(txID); - } - transactionToRetryCountMap.put(txID, currentCount); - } - } - - if (!txIDsToUpdate.isEmpty()) { - deletedBlockLogStateManager - .increaseRetryCountOfTransactionInDB(txIDsToUpdate); - } + transactionStatusManager.incrementRetryCount(txIDs, maxRetry); } finally { lock.unlock(); } @@ -207,9 +186,7 @@ public int resetCount(List txIDs) throws IOException { .map(DeletedBlocksTransaction::getTxID) .collect(Collectors.toList()); } - for (Long txID: txIDs) { - transactionToRetryCountMap.computeIfPresent(txID, (key, value) -> 0); - } + transactionStatusManager.resetRetryCount(txIDs); return deletedBlockLogStateManager.resetRetryCountOfTransactionInDB( new ArrayList<>(new HashSet<>(txIDs))); } finally { @@ -227,89 +204,6 @@ private DeletedBlocksTransaction constructNewTransaction( .build(); } - /** - * {@inheritDoc} - * - * @param transactionResults - transaction IDs. - * @param dnID - Id of Datanode which has acknowledged - * a delete block command. - * @throws IOException - */ - @Override - public void commitTransactions( - List transactionResults, UUID dnID) { - lock.lock(); - try { - ArrayList txIDsToBeDeleted = new ArrayList<>(); - Set dnsWithCommittedTxn; - for (DeleteBlockTransactionResult transactionResult : - transactionResults) { - if (isTransactionFailed(transactionResult)) { - metrics.incrBlockDeletionTransactionFailure(); - continue; - } - try { - metrics.incrBlockDeletionTransactionSuccess(); - long txID = transactionResult.getTxID(); - // set of dns which have successfully committed transaction txId. - dnsWithCommittedTxn = transactionToDNsCommitMap.get(txID); - final ContainerID containerId = ContainerID.valueOf( - transactionResult.getContainerID()); - if (dnsWithCommittedTxn == null) { - // Mostly likely it's a retried delete command response. - if (LOG.isDebugEnabled()) { - LOG.debug( - "Transaction txId={} commit by dnId={} for containerID={}" - + " failed. Corresponding entry not found.", txID, dnID, - containerId); - } - continue; - } - - dnsWithCommittedTxn.add(dnID); - final ContainerInfo container = - containerManager.getContainer(containerId); - final Set replicas = - containerManager.getContainerReplicas(containerId); - // The delete entry can be safely removed from the log if all the - // corresponding nodes commit the txn. It is required to check that - // the nodes returned in the pipeline match the replication factor. - if (min(replicas.size(), dnsWithCommittedTxn.size()) - >= container.getReplicationConfig().getRequiredNodes()) { - List containerDns = replicas.stream() - .map(ContainerReplica::getDatanodeDetails) - .map(DatanodeDetails::getUuid) - .collect(Collectors.toList()); - if (dnsWithCommittedTxn.containsAll(containerDns)) { - transactionToDNsCommitMap.remove(txID); - transactionToRetryCountMap.remove(txID); - if (LOG.isDebugEnabled()) { - LOG.debug("Purging txId={} from block deletion log", txID); - } - txIDsToBeDeleted.add(txID); - } - } - if (LOG.isDebugEnabled()) { - LOG.debug("Datanode txId={} containerId={} committed by dnId={}", - txID, containerId, dnID); - } - } catch (IOException e) { - LOG.warn("Could not commit delete block transaction: " + - transactionResult.getTxID(), e); - } - } - try { - deletedBlockLogStateManager.removeTransactionsFromDB(txIDsToBeDeleted); - metrics.incrBlockDeletionTransactionCompleted(txIDsToBeDeleted.size()); - } catch (IOException e) { - LOG.warn("Could not commit delete block transactions: " - + txIDsToBeDeleted, e); - } - } finally { - lock.unlock(); - } - } - private boolean isTransactionFailed(DeleteBlockTransactionResult result) { if (LOG.isDebugEnabled()) { LOG.debug( @@ -348,7 +242,7 @@ public int getNumOfValidTransactions() throws IOException { @Override public void reinitialize( Table deletedTable) { - // we don't need handle transactionToDNsCommitMap and + // we don't need to handle SCMDeletedBlockTransactionStatusManager and // deletedBlockLogStateManager, since they will be cleared // when becoming leader. deletedBlockLogStateManager.reinitialize(deletedTable); @@ -359,8 +253,7 @@ public void reinitialize( * leader. */ public void onBecomeLeader() { - transactionToDNsCommitMap.clear(); - transactionToRetryCountMap.clear(); + transactionStatusManager.clear(); } /** @@ -404,23 +297,21 @@ public void close() throws IOException { private void getTransaction(DeletedBlocksTransaction tx, DatanodeDeletedBlockTransactions transactions, - Set dnList, Set replicas) { + Set dnList, Set replicas, + Map> commandStatus) { DeletedBlocksTransaction updatedTxn = DeletedBlocksTransaction.newBuilder(tx) - .setCount(transactionToRetryCountMap.getOrDefault(tx.getTxID(), 0)) + .setCount(transactionStatusManager.getOrDefaultRetryCount( + tx.getTxID(), 0)) .build(); for (ContainerReplica replica : replicas) { - UUID dnID = replica.getDatanodeDetails().getUuid(); - if (!dnList.contains(replica.getDatanodeDetails())) { + DatanodeDetails details = replica.getDatanodeDetails(); + if (!dnList.contains(details)) { continue; } - Set dnsWithTransactionCommitted = - transactionToDNsCommitMap.get(updatedTxn.getTxID()); - if (dnsWithTransactionCommitted == null || !dnsWithTransactionCommitted - .contains(dnID)) { - // Transaction need not be sent to dns which have - // already committed it - transactions.addTransactionToDN(dnID, updatedTxn); + if (!transactionStatusManager.isDuplication( + details, updatedTxn.getTxID(), commandStatus)) { + transactions.addTransactionToDN(details.getUuid(), updatedTxn); } } } @@ -442,15 +333,26 @@ public DatanodeDeletedBlockTransactions getTransactions( throws IOException { lock.lock(); try { + // Here we can clean up the Datanode timeout command that no longer + // reports heartbeats + getSCMDeletedBlockTransactionStatusManager().cleanAllTimeoutSCMCommand( + scmCommandTimeoutMs); DatanodeDeletedBlockTransactions transactions = new DatanodeDeletedBlockTransactions(); try (TableIterator> iter = deletedBlockLogStateManager.getReadOnlyIterator()) { + // Get the CmdStatus status of the aggregation, so that the current + // status of the specified transaction can be found faster + Map> commandStatus = + getSCMDeletedBlockTransactionStatusManager() + .getCommandStatusByTxId(dnList.stream(). + map(DatanodeDetails::getUuid).collect(Collectors.toSet())); ArrayList txIDs = new ArrayList<>(); // Here takes block replica count as the threshold to avoid the case // that part of replicas committed the TXN and recorded in the - // transactionToDNsCommitMap, while they are counted in the threshold. + // SCMDeletedBlockTransactionStatusManager, while they are counted + // in the threshold. while (iter.hasNext() && transactions.getBlocksDeleted() < blockDeletionLimit) { Table.KeyValue keyValue = iter.next(); @@ -471,9 +373,8 @@ public DatanodeDeletedBlockTransactions getTransactions( if (checkInadequateReplica(replicas, txn)) { continue; } - getTransaction(txn, transactions, dnList, replicas); - transactionToDNsCommitMap - .putIfAbsent(txn.getTxID(), new LinkedHashSet<>()); + getTransaction( + txn, transactions, dnList, replicas, commandStatus); } } catch (ContainerNotFoundException ex) { LOG.warn("Container: " + id + " was not found for the transaction: " @@ -492,6 +393,33 @@ public DatanodeDeletedBlockTransactions getTransactions( } } + public void setScmCommandTimeoutMs(long scmCommandTimeoutMs) { + this.scmCommandTimeoutMs = scmCommandTimeoutMs; + } + + @VisibleForTesting + public SCMDeletedBlockTransactionStatusManager + getSCMDeletedBlockTransactionStatusManager() { + return transactionStatusManager; + } + + @Override + public void recordTransactionCreated(UUID dnId, long scmCmdId, + Set dnTxSet) { + getSCMDeletedBlockTransactionStatusManager() + .recordTransactionCreated(dnId, scmCmdId, dnTxSet); + } + + @Override + public void onDatanodeDead(UUID dnId) { + getSCMDeletedBlockTransactionStatusManager().onDatanodeDead(dnId); + } + + @Override + public void onSent(DatanodeDetails dnId, SCMCommand scmCommand) { + getSCMDeletedBlockTransactionStatusManager().onSent(dnId, scmCommand); + } + @Override public void onMessage( DeleteBlockStatus deleteBlockStatus, EventPublisher publisher) { @@ -500,18 +428,30 @@ public void onMessage( return; } - CommandStatus.Status status = deleteBlockStatus.getCmdStatus().getStatus(); - if (status == CommandStatus.Status.EXECUTED) { - ContainerBlocksDeletionACKProto ackProto = - deleteBlockStatus.getCmdStatus().getBlockDeletionAck(); - commitTransactions(ackProto.getResultsList(), - UUID.fromString(ackProto.getDnId())); - metrics.incrBlockDeletionCommandSuccess(); - } else if (status == CommandStatus.Status.FAILED) { - metrics.incrBlockDeletionCommandFailure(); - } else { - LOG.error("Delete Block Command is not executed yet."); - return; + DatanodeDetails details = deleteBlockStatus.getDatanodeDetails(); + UUID dnId = details.getUuid(); + for (CommandStatus commandStatus : deleteBlockStatus.getCmdStatus()) { + CommandStatus.Status status = commandStatus.getStatus(); + lock.lock(); + try { + if (status == CommandStatus.Status.EXECUTED) { + ContainerBlocksDeletionACKProto ackProto = + commandStatus.getBlockDeletionAck(); + getSCMDeletedBlockTransactionStatusManager() + .commitTransactions(ackProto.getResultsList(), dnId); + metrics.incrBlockDeletionCommandSuccess(); + } else if (status == CommandStatus.Status.FAILED) { + metrics.incrBlockDeletionCommandFailure(); + } else { + LOG.debug("Delete Block Command {} is not executed on the Datanode" + + " {}.", commandStatus.getCmdId(), dnId); + } + + getSCMDeletedBlockTransactionStatusManager() + .commitSCMCommandStatus(deleteBlockStatus.getCmdStatus(), dnId); + } finally { + lock.unlock(); + } } } } diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/SCMBlockDeletingService.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/SCMBlockDeletingService.java index be20dbd61bd6..7271d9dcba68 100644 --- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/SCMBlockDeletingService.java +++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/SCMBlockDeletingService.java @@ -42,14 +42,15 @@ import org.apache.hadoop.hdds.scm.ha.SCMServiceManager; import org.apache.hadoop.hdds.scm.node.NodeManager; import org.apache.hadoop.hdds.scm.node.NodeStatus; +import org.apache.hadoop.hdds.scm.node.states.NodeNotFoundException; import org.apache.hadoop.hdds.server.events.EventPublisher; import org.apache.hadoop.hdds.utils.BackgroundService; import org.apache.hadoop.hdds.utils.BackgroundTask; import org.apache.hadoop.hdds.utils.BackgroundTaskQueue; import org.apache.hadoop.hdds.utils.BackgroundTaskResult.EmptyTaskResult; +import org.apache.hadoop.ozone.container.common.statemachine.DatanodeConfiguration; import org.apache.hadoop.ozone.protocol.commands.CommandForDatanode; import org.apache.hadoop.ozone.protocol.commands.DeleteBlocksCommand; -import org.apache.hadoop.ozone.protocol.commands.SCMCommand; import org.apache.hadoop.util.Time; import com.google.common.annotations.VisibleForTesting; @@ -91,6 +92,7 @@ public class SCMBlockDeletingService extends BackgroundService private long safemodeExitMillis = 0; private final long safemodeExitRunDelayMillis; + private final long deleteBlocksPendingCommandLimit; private final Clock clock; @SuppressWarnings("parameternumber") @@ -111,6 +113,9 @@ public SCMBlockDeletingService(DeletedBlockLog deletedBlockLog, HddsConfigKeys.HDDS_SCM_WAIT_TIME_AFTER_SAFE_MODE_EXIT, HddsConfigKeys.HDDS_SCM_WAIT_TIME_AFTER_SAFE_MODE_EXIT_DEFAULT, TimeUnit.MILLISECONDS); + DatanodeConfiguration dnConf = + conf.getObject(DatanodeConfiguration.class); + this.deleteBlocksPendingCommandLimit = dnConf.getBlockDeleteQueueLimit(); this.clock = clock; this.deletedBlockLog = deletedBlockLog; this.nodeManager = nodeManager; @@ -156,13 +161,12 @@ public EmptyTaskResult call() throws Exception { List datanodes = nodeManager.getNodes(NodeStatus.inServiceHealthy()); if (datanodes != null) { - // When DN node is healthy and in-service, and previous commands - // are handled for deleteBlocks Type, then it will be considered - // in this iteration - final Set included = datanodes.stream().filter( - dn -> nodeManager.getCommandQueueCount(dn.getUuid(), - Type.deleteBlocksCommand) == 0).collect(Collectors.toSet()); try { + // When DN node is healthy and in-service, and their number of + // 'deleteBlocks' type commands is below the limit. + // These nodes will be considered for this iteration. + final Set included = + getDatanodesWithinCommandLimit(datanodes); DatanodeDeletedBlockTransactions transactions = deletedBlockLog.getTransactions(getBlockDeleteTXNum(), included); @@ -176,11 +180,14 @@ public EmptyTaskResult call() throws Exception { UUID dnId = entry.getKey(); List dnTXs = entry.getValue(); if (!dnTXs.isEmpty()) { - processedTxIDs.addAll(dnTXs.stream() + Set dnTxSet = dnTXs.stream() .map(DeletedBlocksTransaction::getTxID) - .collect(Collectors.toSet())); - SCMCommand command = new DeleteBlocksCommand(dnTXs); + .collect(Collectors.toSet()); + processedTxIDs.addAll(dnTxSet); + DeleteBlocksCommand command = new DeleteBlocksCommand(dnTXs); command.setTerm(scmContext.getTermOfLeader()); + deletedBlockLog.recordTransactionCreated(dnId, command.getId(), + dnTxSet); eventPublisher.fireEvent(SCMEvents.DATANODE_COMMAND, new CommandForDatanode<>(dnId, command)); metrics.incrBlockDeletionCommandSent(); @@ -203,7 +210,8 @@ public EmptyTaskResult call() throws Exception { deletedBlockLog.incrementCount(new ArrayList<>(processedTxIDs)); } catch (NotLeaderException nle) { LOG.warn("Skip current run, since not leader any more.", nle); - return EmptyTaskResult.newResult(); + } catch (NodeNotFoundException e) { + LOG.error("Datanode not found in NodeManager. Should not happen", e); } catch (IOException e) { // We may tolerate a number of failures for sometime // but if it continues to fail, at some point we need to raise @@ -211,7 +219,6 @@ public EmptyTaskResult call() throws Exception { // continues to retry the scanning. LOG.error("Failed to get block deletion transactions from delTX log", e); - return EmptyTaskResult.newResult(); } } @@ -281,4 +288,24 @@ public void stop() { public ScmBlockDeletingServiceMetrics getMetrics() { return this.metrics; } + + /** + * Filters and returns a set of healthy datanodes that have not exceeded + * the deleteBlocksPendingCommandLimit. + * + * @param datanodes a list of DatanodeDetails + * @return a set of filtered DatanodeDetails + */ + @VisibleForTesting + protected Set getDatanodesWithinCommandLimit( + List datanodes) throws NodeNotFoundException { + final Set included = new HashSet<>(); + for (DatanodeDetails dn : datanodes) { + if (nodeManager.getTotalDatanodeCommandCount(dn, Type.deleteBlocksCommand) < deleteBlocksPendingCommandLimit + && nodeManager.getCommandQueueCount(dn.getUuid(), Type.deleteBlocksCommand) < 2) { + included.add(dn); + } + } + return included; + } } diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/SCMDeletedBlockTransactionStatusManager.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/SCMDeletedBlockTransactionStatusManager.java new file mode 100644 index 000000000000..d03062113509 --- /dev/null +++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/SCMDeletedBlockTransactionStatusManager.java @@ -0,0 +1,581 @@ +/* + * 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.hdds.scm.block; + +import com.google.common.annotations.VisibleForTesting; +import org.apache.hadoop.hdds.protocol.DatanodeDetails; +import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.CommandStatus; +import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.ContainerBlocksDeletionACKProto.DeleteBlockTransactionResult; +import org.apache.hadoop.hdds.scm.container.ContainerID; +import org.apache.hadoop.hdds.scm.container.ContainerInfo; +import org.apache.hadoop.hdds.scm.container.ContainerManager; +import org.apache.hadoop.hdds.scm.container.ContainerReplica; +import org.apache.hadoop.hdds.scm.ha.SCMContext; +import org.apache.hadoop.ozone.protocol.commands.SCMCommand; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +import static java.lang.Math.min; +import static org.apache.hadoop.hdds.scm.block.SCMDeletedBlockTransactionStatusManager.SCMDeleteBlocksCommandStatusManager.CmdStatus; +import static org.apache.hadoop.hdds.scm.block.SCMDeletedBlockTransactionStatusManager.SCMDeleteBlocksCommandStatusManager.CmdStatus.SENT; +import static org.apache.hadoop.hdds.scm.block.SCMDeletedBlockTransactionStatusManager.SCMDeleteBlocksCommandStatusManager.CmdStatus.TO_BE_SENT; + +/** + * This is a class to manage the status of DeletedBlockTransaction, + * the purpose of this class is to reduce the number of duplicate + * DeletedBlockTransaction sent to the DN. + */ +public class SCMDeletedBlockTransactionStatusManager { + public static final Logger LOG = + LoggerFactory.getLogger(SCMDeletedBlockTransactionStatusManager.class); + // Maps txId to set of DNs which are successful in committing the transaction + private final Map> transactionToDNsCommitMap; + // Maps txId to its retry counts; + private final Map transactionToRetryCountMap; + // The access to DeletedBlocksTXTable is protected by + // DeletedBlockLogStateManager. + private final DeletedBlockLogStateManager deletedBlockLogStateManager; + private final ContainerManager containerManager; + private final ScmBlockDeletingServiceMetrics metrics; + private final SCMContext scmContext; + private final long scmCommandTimeoutMs; + + /** + * Before the DeletedBlockTransaction is executed on DN and reported to + * SCM, it is managed by this {@link SCMDeleteBlocksCommandStatusManager}. + * After the DeletedBlocksTransaction in the DeleteBlocksCommand is + * committed on the SCM, it is managed by + * {@link SCMDeletedBlockTransactionStatusManager#transactionToDNsCommitMap} + */ + private final SCMDeleteBlocksCommandStatusManager + scmDeleteBlocksCommandStatusManager; + + public SCMDeletedBlockTransactionStatusManager( + DeletedBlockLogStateManager deletedBlockLogStateManager, + ContainerManager containerManager, SCMContext scmContext, + ScmBlockDeletingServiceMetrics metrics, long scmCommandTimeoutMs) { + // maps transaction to dns which have committed it. + this.deletedBlockLogStateManager = deletedBlockLogStateManager; + this.metrics = metrics; + this.containerManager = containerManager; + this.scmContext = scmContext; + this.scmCommandTimeoutMs = scmCommandTimeoutMs; + this.transactionToDNsCommitMap = new ConcurrentHashMap<>(); + this.transactionToRetryCountMap = new ConcurrentHashMap<>(); + this.scmDeleteBlocksCommandStatusManager = + new SCMDeleteBlocksCommandStatusManager(); + } + + /** + * A class that manages the status of a DeletedBlockTransaction based + * on DeleteBlocksCommand. + */ + protected static class SCMDeleteBlocksCommandStatusManager { + public static final Logger LOG = + LoggerFactory.getLogger(SCMDeleteBlocksCommandStatusManager.class); + private final Map> scmCmdStatusRecord; + + private static final CmdStatus DEFAULT_STATUS = TO_BE_SENT; + private static final Set STATUSES_REQUIRING_TIMEOUT = + new HashSet<>(Arrays.asList(SENT)); + + public SCMDeleteBlocksCommandStatusManager() { + this.scmCmdStatusRecord = new ConcurrentHashMap<>(); + } + + /** + * Status of SCMDeleteBlocksCommand. + */ + public enum CmdStatus { + // The DeleteBlocksCommand has not yet been sent. + // This is the initial status of the command after it's created. + TO_BE_SENT, + // If the DeleteBlocksCommand has been sent but has not been executed + // completely by DN, the DeleteBlocksCommand's state will be SENT. + // Note that the state of SENT includes the following possibilities. + // - The command was sent but not received + // - The command was sent and received by the DN, + // and is waiting to be executed. + // - The Command sent and being executed by DN + SENT + } + + protected static final class CmdStatusData { + private final UUID dnId; + private final long scmCmdId; + private final Set deletedBlocksTxIds; + private Instant updateTime; + private CmdStatus status; + + private CmdStatusData( + UUID dnId, long scmTxID, Set deletedBlocksTxIds) { + this.dnId = dnId; + this.scmCmdId = scmTxID; + this.deletedBlocksTxIds = deletedBlocksTxIds; + setStatus(DEFAULT_STATUS); + } + + public Set getDeletedBlocksTxIds() { + return Collections.unmodifiableSet(deletedBlocksTxIds); + } + + public UUID getDnId() { + return dnId; + } + + public long getScmCmdId() { + return scmCmdId; + } + + public CmdStatus getStatus() { + return status; + } + + public void setStatus(CmdStatus status) { + this.updateTime = Instant.now(); + this.status = status; + } + + public Instant getUpdateTime() { + return updateTime; + } + + @Override + public String toString() { + return "ScmTxStateMachine" + + "{dnId=" + dnId + + ", scmTxID=" + scmCmdId + + ", deletedBlocksTxIds=" + deletedBlocksTxIds + + ", updateTime=" + updateTime + + ", status=" + status + + '}'; + } + } + + protected static CmdStatusData createScmCmdStatusData( + UUID dnId, long scmCmdId, Set deletedBlocksTxIds) { + return new CmdStatusData(dnId, scmCmdId, deletedBlocksTxIds); + } + + protected void recordScmCommand(CmdStatusData statusData) { + LOG.debug("Record ScmCommand: {}", statusData); + scmCmdStatusRecord.computeIfAbsent(statusData.getDnId(), k -> + new ConcurrentHashMap<>()).put(statusData.getScmCmdId(), statusData); + } + + protected void onSent(UUID dnId, long scmCmdId) { + updateStatus(dnId, scmCmdId, CommandStatus.Status.PENDING); + } + + protected void onDatanodeDead(UUID dnId) { + LOG.info("Clean SCMCommand record for Datanode: {}", dnId); + scmCmdStatusRecord.remove(dnId); + } + + protected void updateStatusByDNCommandStatus(UUID dnId, long scmCmdId, + CommandStatus.Status newState) { + updateStatus(dnId, scmCmdId, newState); + } + + protected void cleanAllTimeoutSCMCommand(long timeoutMs) { + for (UUID dnId : scmCmdStatusRecord.keySet()) { + for (CmdStatus status : STATUSES_REQUIRING_TIMEOUT) { + removeTimeoutScmCommand( + dnId, getScmCommandIds(dnId, status), timeoutMs); + } + } + } + + public void cleanTimeoutSCMCommand(UUID dnId, long timeoutMs) { + for (CmdStatus status : STATUSES_REQUIRING_TIMEOUT) { + removeTimeoutScmCommand( + dnId, getScmCommandIds(dnId, status), timeoutMs); + } + } + + private Set getScmCommandIds(UUID dnId, CmdStatus status) { + Set scmCmdIds = new HashSet<>(); + Map record = scmCmdStatusRecord.get(dnId); + if (record == null) { + return scmCmdIds; + } + for (CmdStatusData statusData : record.values()) { + if (statusData.getStatus().equals(status)) { + scmCmdIds.add(statusData.getScmCmdId()); + } + } + return scmCmdIds; + } + + private Instant getUpdateTime(UUID dnId, long scmCmdId) { + Map record = scmCmdStatusRecord.get(dnId); + if (record == null || record.get(scmCmdId) == null) { + return null; + } + return record.get(scmCmdId).getUpdateTime(); + } + + private void updateStatus(UUID dnId, long scmCmdId, + CommandStatus.Status newStatus) { + Map recordForDn = scmCmdStatusRecord.get(dnId); + if (recordForDn == null) { + LOG.warn("Unknown Datanode: {} Scm Command ID: {} report status {}", + dnId, scmCmdId, newStatus); + return; + } + if (recordForDn.get(scmCmdId) == null) { + // Because of the delay in the DN report, the DN sometimes report obsolete + // Command status that are cleared by the SCM. + LOG.debug("Unknown SCM Command ID: {} Datanode: {} report status {}", + scmCmdId, dnId, newStatus); + return; + } + + boolean changed = false; + CmdStatusData statusData = recordForDn.get(scmCmdId); + CmdStatus oldStatus = statusData.getStatus(); + switch (newStatus) { + case PENDING: + if (oldStatus == TO_BE_SENT || oldStatus == SENT) { + // TO_BE_SENT -> SENT: The DeleteBlocksCommand is sent by SCM, + // The follow-up status has not been updated by Datanode. + + // SENT -> SENT: The DeleteBlocksCommand continues to wait to be + // executed by Datanode. + statusData.setStatus(SENT); + changed = true; + } + break; + case EXECUTED: + case FAILED: + if (oldStatus == SENT) { + // Once the DN executes DeleteBlocksCommands, regardless of whether + // DeleteBlocksCommands is executed successfully or not, + // it will be deleted from record. + // Successful DeleteBlocksCommands are recorded in + // `transactionToDNsCommitMap`. + removeScmCommand(dnId, scmCmdId); + changed = true; + } + if (oldStatus == TO_BE_SENT) { + // SCM receives a reply to an unsent transaction, + // which should not normally occur. + LOG.error("Received {} status for a command marked TO_BE_SENT. " + + "This indicates a potential issue in command handling. " + + "SCM Command ID: {}, Datanode: {}, Current status: {}", + newStatus, scmCmdId, dnId, oldStatus); + removeScmCommand(dnId, scmCmdId); + changed = true; + } + break; + default: + LOG.error("Unexpected status from Datanode: {}. SCM Command ID: {} with status: {}.", + dnId, scmCmdId, newStatus); + break; + } + if (!changed) { + LOG.warn("Cannot update illegal status for Datanode: {} SCM Command ID: {} " + + "status {} by DN report status {}", dnId, scmCmdId, oldStatus, newStatus); + } else { + LOG.debug("Successful update Datanode: {} SCM Command ID: {} status From {} to" + + " {}, DN report status {}", dnId, scmCmdId, oldStatus, statusData.getStatus(), newStatus); + } + } + + private void removeTimeoutScmCommand(UUID dnId, + Set scmCmdIds, long timeoutMs) { + Instant now = Instant.now(); + for (Long scmCmdId : scmCmdIds) { + Instant updateTime = getUpdateTime(dnId, scmCmdId); + if (updateTime != null && + Duration.between(updateTime, now).toMillis() > timeoutMs) { + CmdStatusData state = removeScmCommand(dnId, scmCmdId); + LOG.warn("SCM BlockDeletionCommand {} for Datanode: {} was removed after {}ms without update", + state, dnId, timeoutMs); + } + } + } + + private CmdStatusData removeScmCommand(UUID dnId, long scmCmdId) { + Map record = scmCmdStatusRecord.get(dnId); + if (record == null || record.get(scmCmdId) == null) { + return null; + } + CmdStatusData statusData = record.remove(scmCmdId); + LOG.debug("Remove ScmCommand {} for Datanode: {} ", statusData, dnId); + return statusData; + } + + public Map> getCommandStatusByTxId( + Set dnIds) { + Map> result = + new HashMap<>(scmCmdStatusRecord.size()); + + for (UUID dnId : dnIds) { + Map record = scmCmdStatusRecord.get(dnId); + if (record == null) { + continue; + } + Map dnStatusMap = new HashMap<>(); + for (CmdStatusData statusData : record.values()) { + CmdStatus status = statusData.getStatus(); + for (Long deletedBlocksTxId : statusData.getDeletedBlocksTxIds()) { + dnStatusMap.put(deletedBlocksTxId, status); + } + } + result.put(dnId, dnStatusMap); + } + + return result; + } + + private void clear() { + scmCmdStatusRecord.clear(); + } + + @VisibleForTesting + Map> getScmCmdStatusRecord() { + return scmCmdStatusRecord; + } + } + + public void incrementRetryCount(List txIDs, long maxRetry) + throws IOException { + ArrayList txIDsToUpdate = new ArrayList<>(); + for (Long txID : txIDs) { + int currentCount = + transactionToRetryCountMap.getOrDefault(txID, 0); + if (currentCount > maxRetry) { + continue; + } else { + currentCount += 1; + if (currentCount > maxRetry) { + txIDsToUpdate.add(txID); + } + transactionToRetryCountMap.put(txID, currentCount); + } + } + + if (!txIDsToUpdate.isEmpty()) { + deletedBlockLogStateManager + .increaseRetryCountOfTransactionInDB(txIDsToUpdate); + } + } + + public void resetRetryCount(List txIDs) throws IOException { + for (Long txID: txIDs) { + transactionToRetryCountMap.computeIfPresent(txID, (key, value) -> 0); + } + } + + public int getOrDefaultRetryCount(long txID, int defaultValue) { + return transactionToRetryCountMap.getOrDefault(txID, defaultValue); + } + + public void onSent(DatanodeDetails dnId, SCMCommand scmCommand) { + scmDeleteBlocksCommandStatusManager.onSent( + dnId.getUuid(), scmCommand.getId()); + } + + public Map> getCommandStatusByTxId( + Set dnIds) { + return scmDeleteBlocksCommandStatusManager.getCommandStatusByTxId(dnIds); + } + + public void recordTransactionCreated( + UUID dnId, long scmCmdId, Set dnTxSet) { + scmDeleteBlocksCommandStatusManager.recordScmCommand( + SCMDeleteBlocksCommandStatusManager + .createScmCmdStatusData(dnId, scmCmdId, dnTxSet)); + dnTxSet.forEach(txId -> transactionToDNsCommitMap + .putIfAbsent(txId, new LinkedHashSet<>())); + } + + public void clear() { + transactionToRetryCountMap.clear(); + scmDeleteBlocksCommandStatusManager.clear(); + transactionToDNsCommitMap.clear(); + } + + public void cleanAllTimeoutSCMCommand(long timeoutMs) { + scmDeleteBlocksCommandStatusManager.cleanAllTimeoutSCMCommand(timeoutMs); + } + + public void onDatanodeDead(UUID dnId) { + scmDeleteBlocksCommandStatusManager.onDatanodeDead(dnId); + } + + public boolean isDuplication(DatanodeDetails dnDetail, long tx, + Map> commandStatus) { + if (alreadyExecuted(dnDetail.getUuid(), tx)) { + return true; + } + return inProcessing(dnDetail.getUuid(), tx, commandStatus); + } + + public boolean alreadyExecuted(UUID dnId, long txId) { + Set dnsWithTransactionCommitted = + transactionToDNsCommitMap.get(txId); + return dnsWithTransactionCommitted != null && dnsWithTransactionCommitted + .contains(dnId); + } + + /** + * Commits a transaction means to delete all footprints of a transaction + * from the log. This method doesn't guarantee all transactions can be + * successfully deleted, it tolerate failures and tries best efforts to. + * @param transactionResults - delete block transaction results. + * @param dnId - ID of datanode which acknowledges the delete block command. + */ + @VisibleForTesting + public void commitTransactions( + List transactionResults, UUID dnId) { + + ArrayList txIDsToBeDeleted = new ArrayList<>(); + Set dnsWithCommittedTxn; + for (DeleteBlockTransactionResult transactionResult : + transactionResults) { + if (isTransactionFailed(transactionResult)) { + metrics.incrBlockDeletionTransactionFailure(); + continue; + } + try { + metrics.incrBlockDeletionTransactionSuccess(); + long txID = transactionResult.getTxID(); + // set of dns which have successfully committed transaction txId. + dnsWithCommittedTxn = transactionToDNsCommitMap.get(txID); + final ContainerID containerId = ContainerID.valueOf( + transactionResult.getContainerID()); + if (dnsWithCommittedTxn == null) { + // Mostly likely it's a retried delete command response. + if (LOG.isDebugEnabled()) { + LOG.debug( + "Transaction txId: {} commit by Datanode: {} for ContainerId: {}" + + " failed. Corresponding entry not found.", txID, dnId, + containerId); + } + continue; + } + + dnsWithCommittedTxn.add(dnId); + final ContainerInfo container = + containerManager.getContainer(containerId); + final Set replicas = + containerManager.getContainerReplicas(containerId); + // The delete entry can be safely removed from the log if all the + // corresponding nodes commit the txn. It is required to check that + // the nodes returned in the pipeline match the replication factor. + if (min(replicas.size(), dnsWithCommittedTxn.size()) + >= container.getReplicationConfig().getRequiredNodes()) { + List containerDns = replicas.stream() + .map(ContainerReplica::getDatanodeDetails) + .map(DatanodeDetails::getUuid) + .collect(Collectors.toList()); + if (dnsWithCommittedTxn.containsAll(containerDns)) { + transactionToDNsCommitMap.remove(txID); + transactionToRetryCountMap.remove(txID); + if (LOG.isDebugEnabled()) { + LOG.debug("Purging txId: {} from block deletion log", txID); + } + txIDsToBeDeleted.add(txID); + } + } + if (LOG.isDebugEnabled()) { + LOG.debug("Datanode txId: {} ContainerId: {} committed by Datanode: {}", + txID, containerId, dnId); + } + } catch (IOException e) { + LOG.warn("Could not commit delete block transaction: " + + transactionResult.getTxID(), e); + } + } + try { + deletedBlockLogStateManager.removeTransactionsFromDB(txIDsToBeDeleted); + metrics.incrBlockDeletionTransactionCompleted(txIDsToBeDeleted.size()); + } catch (IOException e) { + LOG.warn("Could not commit delete block transactions: " + + txIDsToBeDeleted, e); + } + } + + @VisibleForTesting + public void commitSCMCommandStatus(List deleteBlockStatus, + UUID dnId) { + processSCMCommandStatus(deleteBlockStatus, dnId); + scmDeleteBlocksCommandStatusManager. + cleanTimeoutSCMCommand(dnId, scmCommandTimeoutMs); + } + + private boolean inProcessing(UUID dnId, long deletedBlocksTxId, + Map> commandStatus) { + Map deletedBlocksTxStatus = commandStatus.get(dnId); + return deletedBlocksTxStatus != null && + deletedBlocksTxStatus.get(deletedBlocksTxId) != null; + } + + private void processSCMCommandStatus(List deleteBlockStatus, + UUID dnID) { + Map lastStatus = new HashMap<>(); + Map summary = new HashMap<>(); + + // The CommandStatus is ordered in the report. So we can focus only on the + // last status in the command report. + deleteBlockStatus.forEach(cmdStatus -> { + lastStatus.put(cmdStatus.getCmdId(), cmdStatus); + summary.put(cmdStatus.getCmdId(), cmdStatus.getStatus()); + }); + LOG.debug("CommandStatus {} from Datanode: {} ", summary, dnID); + for (Map.Entry entry : lastStatus.entrySet()) { + CommandStatus.Status status = entry.getValue().getStatus(); + scmDeleteBlocksCommandStatusManager.updateStatusByDNCommandStatus( + dnID, entry.getKey(), status); + } + } + + private boolean isTransactionFailed(DeleteBlockTransactionResult result) { + if (LOG.isDebugEnabled()) { + LOG.debug( + "Got block deletion ACK from datanode, TXIDs {}, " + "success {}", + result.getTxID(), result.getSuccess()); + } + if (!result.getSuccess()) { + LOG.warn("Got failed ACK for TXID {}, prepare to resend the " + + "TX in next interval", result.getTxID()); + return true; + } + return false; + } +} diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/command/CommandStatusReportHandler.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/command/CommandStatusReportHandler.java index d43311265d7d..5d737659ddce 100644 --- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/command/CommandStatusReportHandler.java +++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/command/CommandStatusReportHandler.java @@ -18,6 +18,8 @@ package org.apache.hadoop.hdds.scm.command; import com.google.common.base.Preconditions; +import org.apache.hadoop.hdds.HddsIdFactory; +import org.apache.hadoop.hdds.protocol.DatanodeDetails; import org.apache.hadoop.hdds.protocol.proto .StorageContainerDatanodeProtocolProtos.SCMCommandProto; import org.apache.hadoop.hdds.protocol.proto @@ -31,6 +33,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; import java.util.List; /** @@ -54,32 +57,43 @@ public void onMessage(CommandStatusReportFromDatanode report, } // Route command status to its watchers. + List deleteBlocksCommandStatus = new ArrayList<>(); cmdStatusList.forEach(cmdStatus -> { if (LOGGER.isTraceEnabled()) { LOGGER.trace("Emitting command status for id:{} type: {}", cmdStatus .getCmdId(), cmdStatus.getType()); } if (cmdStatus.getType() == SCMCommandProto.Type.deleteBlocksCommand) { - publisher.fireEvent(SCMEvents.DELETE_BLOCK_STATUS, - new DeleteBlockStatus(cmdStatus)); + deleteBlocksCommandStatus.add(cmdStatus); } else { LOGGER.debug("CommandStatus of type:{} not handled in " + "CommandStatusReportHandler.", cmdStatus.getType()); } }); + + /** + * The purpose of aggregating all CommandStatus to commit is to reduce the + * Thread switching. When the Datanode queue has a large number of commands + * , there will have many {@link CommandStatus#Status#PENDING} status + * CommandStatus in report + */ + if (!deleteBlocksCommandStatus.isEmpty()) { + publisher.fireEvent(SCMEvents.DELETE_BLOCK_STATUS, new DeleteBlockStatus( + deleteBlocksCommandStatus, report.getDatanodeDetails())); + } } /** * Wrapper event for CommandStatus. */ public static class CommandStatusEvent implements IdentifiableEventPayload { - private CommandStatus cmdStatus; + private final List cmdStatus; - CommandStatusEvent(CommandStatus cmdStatus) { + CommandStatusEvent(List cmdStatus) { this.cmdStatus = cmdStatus; } - public CommandStatus getCmdStatus() { + public List getCmdStatus() { return cmdStatus; } @@ -90,7 +104,7 @@ public String toString() { @Override public long getId() { - return cmdStatus.getCmdId(); + return HddsIdFactory.getLongId(); } } @@ -98,8 +112,16 @@ public long getId() { * Wrapper event for DeleteBlock Command. */ public static class DeleteBlockStatus extends CommandStatusEvent { - public DeleteBlockStatus(CommandStatus cmdStatus) { + private final DatanodeDetails datanodeDetails; + + public DeleteBlockStatus(List cmdStatus, + DatanodeDetails datanodeDetails) { super(cmdStatus); + this.datanodeDetails = datanodeDetails; + } + + public DatanodeDetails getDatanodeDetails() { + return datanodeDetails; } } diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/balancer/ContainerBalancerTask.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/balancer/ContainerBalancerTask.java index 6541d75d2793..abbc50ac86a5 100644 --- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/balancer/ContainerBalancerTask.java +++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/balancer/ContainerBalancerTask.java @@ -932,7 +932,7 @@ private long ratioToBytes(Long nodeCapacity, double utilizationRatio) { return 0; } SCMNodeStat aggregatedStats = new SCMNodeStat( - 0, 0, 0); + 0, 0, 0, 0, 0); for (DatanodeUsageInfo node : nodes) { aggregatedStats.add(node.getScmNodeStat()); } diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/placement/metrics/NodeStat.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/placement/metrics/NodeStat.java index d6857d395cfb..eedc89dfc585 100644 --- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/placement/metrics/NodeStat.java +++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/placement/metrics/NodeStat.java @@ -42,6 +42,18 @@ interface NodeStat { */ LongMetric getRemaining(); + /** + * Get the committed space of the node. + * @return the committed space of the node + */ + LongMetric getCommitted(); + + /** + * Get a min free space available to spare on the node. + * @return a min free space available to spare + */ + LongMetric getFreeSpaceToSpare(); + /** * Set the total/used/remaining space. * @param capacity - total space. @@ -49,7 +61,8 @@ interface NodeStat { * @param remain - remaining space. */ @VisibleForTesting - void set(long capacity, long used, long remain); + void set(long capacity, long used, long remain, long committed, + long freeSpaceToSpare); /** * Adding of the stat. diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/placement/metrics/SCMNodeMetric.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/placement/metrics/SCMNodeMetric.java index 2f5c6f33f73e..330bf67416ae 100644 --- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/placement/metrics/SCMNodeMetric.java +++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/placement/metrics/SCMNodeMetric.java @@ -36,16 +36,19 @@ public SCMNodeMetric(SCMNodeStat stat) { } /** - * Set the capacity, used and remaining space on a datanode. + * Set the capacity, used, remaining and committed space on a datanode. * - * @param capacity in bytes - * @param used in bytes + * @param capacity in bytes + * @param used in bytes * @param remaining in bytes + * @param committed + * @paaram committed in bytes */ @VisibleForTesting - public SCMNodeMetric(long capacity, long used, long remaining) { + public SCMNodeMetric(long capacity, long used, long remaining, + long committed, long freeSpaceToSpare) { this.stat = new SCMNodeStat(); - this.stat.set(capacity, used, remaining); + this.stat.set(capacity, used, remaining, committed, freeSpaceToSpare); } /** @@ -156,7 +159,8 @@ public SCMNodeStat get() { @Override public void set(SCMNodeStat value) { stat.set(value.getCapacity().get(), value.getScmUsed().get(), - value.getRemaining().get()); + value.getRemaining().get(), value.getCommitted().get(), + value.getFreeSpaceToSpare().get()); } /** diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/placement/metrics/SCMNodeStat.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/placement/metrics/SCMNodeStat.java index 962bbb464ecc..2a848a04eff5 100644 --- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/placement/metrics/SCMNodeStat.java +++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/container/placement/metrics/SCMNodeStat.java @@ -28,16 +28,20 @@ public class SCMNodeStat implements NodeStat { private LongMetric capacity; private LongMetric scmUsed; private LongMetric remaining; + private LongMetric committed; + private LongMetric freeSpaceToSpare; public SCMNodeStat() { - this(0L, 0L, 0L); + this(0L, 0L, 0L, 0L, 0L); } public SCMNodeStat(SCMNodeStat other) { - this(other.capacity.get(), other.scmUsed.get(), other.remaining.get()); + this(other.capacity.get(), other.scmUsed.get(), other.remaining.get(), + other.committed.get(), other.freeSpaceToSpare.get()); } - public SCMNodeStat(long capacity, long used, long remaining) { + public SCMNodeStat(long capacity, long used, long remaining, long committed, + long freeSpaceToSpare) { Preconditions.checkArgument(capacity >= 0, "Capacity cannot be " + "negative."); Preconditions.checkArgument(used >= 0, "used space cannot be " + @@ -47,6 +51,8 @@ public SCMNodeStat(long capacity, long used, long remaining) { this.capacity = new LongMetric(capacity); this.scmUsed = new LongMetric(used); this.remaining = new LongMetric(remaining); + this.committed = new LongMetric(committed); + this.freeSpaceToSpare = new LongMetric(freeSpaceToSpare); } /** @@ -73,6 +79,24 @@ public LongMetric getRemaining() { return remaining; } + /** + * + * @return the total committed space on the node + */ + @Override + public LongMetric getCommitted() { + return committed; + } + + /** + * Get a min space available to spare on the node. + * @return a min free space available to spare on the node + */ + @Override + public LongMetric getFreeSpaceToSpare() { + return freeSpaceToSpare; + } + /** * Set the capacity, used and remaining space on a datanode. * @@ -82,7 +106,8 @@ public LongMetric getRemaining() { */ @Override @VisibleForTesting - public void set(long newCapacity, long newUsed, long newRemaining) { + public void set(long newCapacity, long newUsed, long newRemaining, + long newCommitted, long newFreeSpaceToSpare) { Preconditions.checkArgument(newCapacity >= 0, "Capacity cannot be " + "negative."); Preconditions.checkArgument(newUsed >= 0, "used space cannot be " + @@ -93,6 +118,8 @@ public void set(long newCapacity, long newUsed, long newRemaining) { this.capacity = new LongMetric(newCapacity); this.scmUsed = new LongMetric(newUsed); this.remaining = new LongMetric(newRemaining); + this.committed = new LongMetric(newCommitted); + this.freeSpaceToSpare = new LongMetric(newFreeSpaceToSpare); } /** @@ -106,6 +133,9 @@ public SCMNodeStat add(NodeStat stat) { this.capacity.set(this.getCapacity().get() + stat.getCapacity().get()); this.scmUsed.set(this.getScmUsed().get() + stat.getScmUsed().get()); this.remaining.set(this.getRemaining().get() + stat.getRemaining().get()); + this.committed.set(this.getCommitted().get() + stat.getCommitted().get()); + this.freeSpaceToSpare.set(this.freeSpaceToSpare.get() + + stat.getFreeSpaceToSpare().get()); return this; } @@ -120,6 +150,9 @@ public SCMNodeStat subtract(NodeStat stat) { this.capacity.set(this.getCapacity().get() - stat.getCapacity().get()); this.scmUsed.set(this.getScmUsed().get() - stat.getScmUsed().get()); this.remaining.set(this.getRemaining().get() - stat.getRemaining().get()); + this.committed.set(this.getCommitted().get() - stat.getCommitted().get()); + this.freeSpaceToSpare.set(freeSpaceToSpare.get() - + stat.getFreeSpaceToSpare().get()); return this; } @@ -129,13 +162,16 @@ public boolean equals(Object to) { SCMNodeStat tempStat = (SCMNodeStat) to; return capacity.isEqual(tempStat.getCapacity().get()) && scmUsed.isEqual(tempStat.getScmUsed().get()) && - remaining.isEqual(tempStat.getRemaining().get()); + remaining.isEqual(tempStat.getRemaining().get()) && + committed.isEqual(tempStat.getCommitted().get()) && + freeSpaceToSpare.isEqual(tempStat.freeSpaceToSpare.get()); } return false; } @Override public int hashCode() { - return Long.hashCode(capacity.get() ^ scmUsed.get() ^ remaining.get()); + return Long.hashCode(capacity.get() ^ scmUsed.get() ^ remaining.get() ^ + committed.get() ^ freeSpaceToSpare.get()); } } diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHAManagerImpl.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHAManagerImpl.java index 54de06efb98a..fc3c1548ba14 100644 --- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHAManagerImpl.java +++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHAManagerImpl.java @@ -96,9 +96,7 @@ public SCMHAManagerImpl(final ConfigurationSource conf, this.transactionBuffer = new SCMHADBTransactionBufferImpl(scm); this.ratisServer = new SCMRatisServerImpl(conf, scm, (SCMHADBTransactionBuffer) transactionBuffer); - this.scmSnapshotProvider = new SCMSnapshotProvider(conf, - scm.getSCMHANodeDetails().getPeerNodeDetails(), - scm.getScmCertificateClient()); + this.scmSnapshotProvider = newScmSnapshotProvider(scm); grpcServer = new InterSCMGrpcProtocolService(conf, scm); } else { this.transactionBuffer = new SCMDBTransactionBufferImpl(); @@ -109,6 +107,13 @@ public SCMHAManagerImpl(final ConfigurationSource conf, } + @VisibleForTesting + protected SCMSnapshotProvider newScmSnapshotProvider(StorageContainerManager storageContainerManager) { + return new SCMSnapshotProvider(storageContainerManager.getConfiguration(), + storageContainerManager.getSCMHANodeDetails().getPeerNodeDetails(), + storageContainerManager.getScmCertificateClient()); + } + /** * {@inheritDoc} */ @@ -194,8 +199,10 @@ public DBCheckpoint downloadCheckpointFromLeader(String leaderId) { } DBCheckpoint dBCheckpoint = getDBCheckpointFromLeader(leaderId); - LOG.info("Downloaded checkpoint from Leader {} to the location {}", - leaderId, dBCheckpoint.getCheckpointLocation()); + if (dBCheckpoint != null) { + LOG.info("Downloaded checkpoint from Leader {} to the location {}", + leaderId, dBCheckpoint.getCheckpointLocation()); + } return dBCheckpoint; } @@ -262,7 +269,7 @@ private DBCheckpoint getDBCheckpointFromLeader(String leaderId) { try { return scmSnapshotProvider.getSCMDBSnapshot(leaderId); - } catch (IOException e) { + } catch (Exception e) { LOG.error("Failed to download checkpoint from SCM leader {}", leaderId, e); } diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMStateMachine.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMStateMachine.java index 52320719e699..54b52925dc0d 100644 --- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMStateMachine.java +++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMStateMachine.java @@ -236,8 +236,8 @@ public CompletableFuture notifyInstallSnapshotFromLeader( String leaderAddress = roleInfoProto.getFollowerInfo() .getLeaderInfo().getId().getAddress(); Optional leaderDetails = - scm.getSCMHANodeDetails().getPeerNodeDetails().stream().filter( - p -> p.getRatisHostPortStr().equals(leaderAddress)) + scm.getSCMHANodeDetails().getPeerNodeDetails().stream() + .filter(p -> p.getRatisHostPortStr().equals(leaderAddress)) .findFirst(); Preconditions.checkState(leaderDetails.isPresent()); final String leaderNodeId = leaderDetails.get().getNodeId(); diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/node/DatanodeUsageInfo.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/node/DatanodeUsageInfo.java index 14353cfa7e37..4f7df4969063 100644 --- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/node/DatanodeUsageInfo.java +++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/node/DatanodeUsageInfo.java @@ -205,6 +205,8 @@ private DatanodeUsageInfoProto.Builder toProtoBuilder(int clientVersion) { builder.setCapacity(scmNodeStat.getCapacity().get()); builder.setUsed(scmNodeStat.getScmUsed().get()); builder.setRemaining(scmNodeStat.getRemaining().get()); + builder.setCommitted(scmNodeStat.getCommitted().get()); + builder.setFreeSpaceToSpare(scmNodeStat.getFreeSpaceToSpare().get()); } builder.setContainerCount(containerCount); diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/node/DeadNodeHandler.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/node/DeadNodeHandler.java index f05eb761d91f..3c40437d7f66 100644 --- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/node/DeadNodeHandler.java +++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/node/DeadNodeHandler.java @@ -24,6 +24,7 @@ import org.apache.hadoop.hdds.protocol.DatanodeDetails; import org.apache.hadoop.hdds.protocol.proto.HddsProtos; +import org.apache.hadoop.hdds.scm.block.DeletedBlockLog; import org.apache.hadoop.hdds.scm.container.ContainerException; import org.apache.hadoop.hdds.scm.container.ContainerInfo; import org.apache.hadoop.hdds.scm.container.ContainerManager; @@ -41,6 +42,8 @@ import com.google.common.base.Preconditions; +import javax.annotation.Nullable; + import static org.apache.hadoop.hdds.scm.events.SCMEvents.CLOSE_CONTAINER; /** @@ -51,6 +54,8 @@ public class DeadNodeHandler implements EventHandler { private final NodeManager nodeManager; private final PipelineManager pipelineManager; private final ContainerManager containerManager; + @Nullable + private final DeletedBlockLog deletedBlockLog; private static final Logger LOG = LoggerFactory.getLogger(DeadNodeHandler.class); @@ -58,9 +63,17 @@ public class DeadNodeHandler implements EventHandler { public DeadNodeHandler(final NodeManager nodeManager, final PipelineManager pipelineManager, final ContainerManager containerManager) { + this(nodeManager, pipelineManager, containerManager, null); + } + + public DeadNodeHandler(final NodeManager nodeManager, + final PipelineManager pipelineManager, + final ContainerManager containerManager, + @Nullable final DeletedBlockLog deletedBlockLog) { this.nodeManager = nodeManager; this.pipelineManager = pipelineManager; this.containerManager = containerManager; + this.deletedBlockLog = deletedBlockLog; } @Override @@ -95,6 +108,13 @@ public void onMessage(final DatanodeDetails datanodeDetails, LOG.info("Clearing command queue of size {} for DN {}", cmdList.size(), datanodeDetails); + // remove DeleteBlocksCommand associated with the dead node unless it + // is IN_MAINTENANCE + if (deletedBlockLog != null && + !nodeManager.getNodeStatus(datanodeDetails).isInMaintenance()) { + deletedBlockLog.onDatanodeDead(datanodeDetails.getUuid()); + } + //move dead datanode out of ClusterNetworkTopology NetworkTopology nt = nodeManager.getClusterNetworkTopologyMap(); if (nt.contains(datanodeDetails)) { diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/node/NodeManager.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/node/NodeManager.java index 011b361d629d..399a7ef952eb 100644 --- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/node/NodeManager.java +++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/node/NodeManager.java @@ -47,6 +47,7 @@ import java.util.Set; import java.util.UUID; import java.util.Collection; +import java.util.function.BiConsumer; /** * A node manager supports a simple interface for managing a datanode. @@ -90,6 +91,18 @@ default RegisteredCommand register( defaultLayoutVersionProto()); } + /** + * Register a SendCommandNotify handler for a specific type of SCMCommand. + * @param type The type of the SCMCommand. + * @param scmCommand A BiConsumer that takes a DatanodeDetails and a + * SCMCommand object and performs the necessary actions. + * @return whatever the regular register command returns with default + * layout version passed in. + */ + default void registerSendCommandNotify(SCMCommandProto.Type type, + BiConsumer> scmCommand) { + } + /** * Gets all Live Datanodes that are currently communicating with SCM. * @param nodeStatus - Status of the node to return diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/node/SCMNodeManager.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/node/SCMNodeManager.java index 167b25afd01c..75b79fc73323 100644 --- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/node/SCMNodeManager.java +++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/node/SCMNodeManager.java @@ -84,6 +84,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.BiConsumer; import java.util.function.Function; import java.util.stream.Collectors; @@ -128,6 +129,8 @@ public class SCMNodeManager implements NodeManager { private final HDDSLayoutVersionManager scmLayoutVersionManager; private final EventPublisher scmNodeEventPublisher; private final SCMContext scmContext; + private final Map>> sendCommandNotifyMap; /** * Lock used to synchronize some operation in Node manager to ensure a @@ -179,6 +182,13 @@ public SCMNodeManager( String dnLimit = conf.get(ScmConfigKeys.OZONE_DATANODE_PIPELINE_LIMIT); this.heavyNodeCriteria = dnLimit == null ? 0 : Integer.parseInt(dnLimit); this.scmContext = scmContext; + this.sendCommandNotifyMap = new HashMap<>(); + } + + @Override + public void registerSendCommandNotify(SCMCommandProto.Type type, + BiConsumer> scmCommand) { + this.sendCommandNotifyMap.put(type, scmCommand); } private void registerMXBean() { @@ -521,6 +531,15 @@ public List processHeartbeat(DatanodeDetails datanodeDetails, commandQueue.getDatanodeCommandSummary(datanodeDetails.getUuid()); List commands = commandQueue.getCommand(datanodeDetails.getUuid()); + + // Update the SCMCommand of deleteBlocksCommand Status + for (SCMCommand command : commands) { + if (sendCommandNotifyMap.get(command.getType()) != null) { + sendCommandNotifyMap.get(command.getType()) + .accept(datanodeDetails, command); + } + } + if (queueReport != null) { processNodeCommandQueueReport(datanodeDetails, queueReport, summary); } @@ -855,13 +874,18 @@ public SCMNodeStat getStats() { long capacity = 0L; long used = 0L; long remaining = 0L; + long committed = 0L; + long freeSpaceToSpare = 0L; for (SCMNodeStat stat : getNodeStats().values()) { capacity += stat.getCapacity().get(); used += stat.getScmUsed().get(); remaining += stat.getRemaining().get(); + committed += stat.getCommitted().get(); + freeSpaceToSpare += stat.getFreeSpaceToSpare().get(); } - return new SCMNodeStat(capacity, used, remaining); + return new SCMNodeStat(capacity, used, remaining, committed, + freeSpaceToSpare); } /** @@ -966,6 +990,8 @@ private SCMNodeStat getNodeStatInternal(DatanodeDetails datanodeDetails) { long capacity = 0L; long used = 0L; long remaining = 0L; + long committed = 0L; + long freeSpaceToSpare = 0L; final DatanodeInfo datanodeInfo = nodeStateManager .getNode(datanodeDetails); @@ -975,8 +1001,11 @@ private SCMNodeStat getNodeStatInternal(DatanodeDetails datanodeDetails) { capacity += reportProto.getCapacity(); used += reportProto.getScmUsed(); remaining += reportProto.getRemaining(); + committed += reportProto.getCommitted(); + freeSpaceToSpare += reportProto.getFreeSpaceToSpare(); } - return new SCMNodeStat(capacity, used, remaining); + return new SCMNodeStat(capacity, used, remaining, committed, + freeSpaceToSpare); } catch (NodeNotFoundException e) { LOG.warn("Cannot generate NodeStat, datanode {} not found.", datanodeDetails.getUuidString()); diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/pipeline/PipelineProvider.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/pipeline/PipelineProvider.java index 4adcd53eb380..0ec74d2405c4 100644 --- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/pipeline/PipelineProvider.java +++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/pipeline/PipelineProvider.java @@ -25,6 +25,7 @@ import java.util.stream.Collectors; import org.apache.hadoop.hdds.client.ReplicationConfig; +import org.apache.hadoop.hdds.conf.ConfigurationSource; import org.apache.hadoop.hdds.protocol.DatanodeDetails; import org.apache.hadoop.hdds.scm.SCMCommonPlacementPolicy; import org.apache.hadoop.hdds.scm.container.ContainerReplica; @@ -85,12 +86,15 @@ protected abstract Pipeline createForRead( protected abstract void shutdown(); List pickNodesNotUsed(REPLICATION_CONFIG replicationConfig, - long metadataSizeRequired, long dataSizeRequired) throws SCMException { + long metadataSizeRequired, + long dataSizeRequired, + ConfigurationSource conf) + throws SCMException { int nodesRequired = replicationConfig.getRequiredNodes(); List healthyDNs = pickAllNodesNotUsed(replicationConfig); List healthyDNsWithSpace = healthyDNs.stream() .filter(dn -> SCMCommonPlacementPolicy - .hasEnoughSpace(dn, metadataSizeRequired, dataSizeRequired)) + .hasEnoughSpace(dn, metadataSizeRequired, dataSizeRequired, conf)) .limit(nodesRequired) .collect(Collectors.toList()); diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/pipeline/RatisPipelineProvider.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/pipeline/RatisPipelineProvider.java index 1b62120c1ee7..8336bce5eae7 100644 --- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/pipeline/RatisPipelineProvider.java +++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/pipeline/RatisPipelineProvider.java @@ -163,7 +163,7 @@ public synchronized Pipeline create(RatisReplicationConfig replicationConfig, switch (factor) { case ONE: dns = pickNodesNotUsed(replicationConfig, minRatisVolumeSizeBytes, - containerSizeBytes); + containerSizeBytes, conf); break; case THREE: List excludeDueToEngagement = filterPipelineEngagement(); diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/pipeline/RatisPipelineUtils.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/pipeline/RatisPipelineUtils.java index 6c68b8088332..04c35cc1feb5 100644 --- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/pipeline/RatisPipelineUtils.java +++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/pipeline/RatisPipelineUtils.java @@ -17,29 +17,16 @@ */ package org.apache.hadoop.hdds.scm.pipeline; -import java.io.IOException; import java.util.List; import java.util.stream.Collectors; import org.apache.hadoop.hdds.client.RatisReplicationConfig; -import org.apache.hadoop.hdds.conf.ConfigurationSource; -import org.apache.hadoop.hdds.protocol.DatanodeDetails; import org.apache.hadoop.hdds.protocol.proto.HddsProtos.ReplicationFactor; -import org.apache.hadoop.hdds.ratis.RatisHelper; -import org.apache.hadoop.hdds.scm.ScmConfigKeys; -import org.apache.ratis.client.RaftClient; -import org.apache.ratis.grpc.GrpcTlsConfig; -import org.apache.ratis.protocol.RaftGroup; -import org.apache.ratis.protocol.RaftGroupId; -import org.apache.ratis.protocol.RaftPeer; -import org.apache.ratis.retry.RetryPolicy; -import org.apache.ratis.rpc.SupportedRpcType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Utility class for Ratis pipelines. Contains methods to create and destroy - * ratis pipelines. + * Utility class for Ratis pipelines. */ public final class RatisPipelineUtils { @@ -48,56 +35,6 @@ public final class RatisPipelineUtils { private RatisPipelineUtils() { } - /** - * Removes pipeline from SCM. Sends ratis command to destroy pipeline on all - * the datanodes. - * - * @param pipeline - Pipeline to be destroyed - * @param ozoneConf - Ozone configuration - * @param grpcTlsConfig - * @throws IOException - */ - public static void destroyPipeline(Pipeline pipeline, - ConfigurationSource ozoneConf, - GrpcTlsConfig grpcTlsConfig) { - final RaftGroup group = RatisHelper.newRaftGroup(pipeline); - if (LOG.isDebugEnabled()) { - LOG.debug("destroying pipeline:{} with {}", pipeline.getId(), group); - } - for (DatanodeDetails dn : pipeline.getNodes()) { - try { - destroyPipeline(dn, pipeline.getId(), ozoneConf, grpcTlsConfig); - } catch (IOException e) { - LOG.warn("Pipeline destroy failed for pipeline={} dn={} exception={}", - pipeline.getId(), dn, e.getMessage()); - } - } - } - - /** - * Sends ratis command to destroy pipeline on the given datanode. - * - * @param dn - Datanode on which pipeline needs to be destroyed - * @param pipelineID - ID of pipeline to be destroyed - * @param ozoneConf - Ozone configuration - * @param grpcTlsConfig - grpc tls configuration - * @throws IOException - */ - static void destroyPipeline(DatanodeDetails dn, PipelineID pipelineID, - ConfigurationSource ozoneConf, GrpcTlsConfig grpcTlsConfig) - throws IOException { - final String rpcType = ozoneConf - .get(ScmConfigKeys.DFS_CONTAINER_RATIS_RPC_TYPE_KEY, - ScmConfigKeys.DFS_CONTAINER_RATIS_RPC_TYPE_DEFAULT); - final RetryPolicy retryPolicy = RatisHelper.createRetryPolicy(ozoneConf); - final RaftPeer p = RatisHelper.toRaftPeer(dn); - try (RaftClient client = RatisHelper - .newRaftClient(SupportedRpcType.valueOfIgnoreCase(rpcType), p, - retryPolicy, grpcTlsConfig, ozoneConf)) { - client.getGroupManagementApi(p.getId()) - .remove(RaftGroupId.valueOf(pipelineID.getId()), true, false); - } - } /** * Return the list of pipelines who share the same set of datanodes diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/pipeline/WritableRatisContainerProvider.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/pipeline/WritableRatisContainerProvider.java index d9474f156d73..f9fc651f2faa 100644 --- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/pipeline/WritableRatisContainerProvider.java +++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/pipeline/WritableRatisContainerProvider.java @@ -32,6 +32,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.annotation.Nullable; import java.io.IOException; import java.util.List; import java.util.stream.Collectors; @@ -79,97 +80,64 @@ public ContainerInfo getContainer(final long size, So we can use different kind of policies. */ - ContainerInfo containerInfo = null; String failureReason = null; //TODO we need to continue the refactor to use repConfig everywhere //in downstream managers. + PipelineRequestInformation req = + PipelineRequestInformation.Builder.getBuilder().setSize(size).build(); - while (true) { - List availablePipelines; - Pipeline pipeline; - // Acquire pipeline manager lock, to avoid any updates to pipeline - // while allocate container happens. This is to avoid scenario like - // mentioned in HDDS-5655. - pipelineManager.acquireReadLock(); - try { - availablePipelines = - findPipelinesByState(repConfig, - excludeList, - Pipeline.PipelineState.OPEN); - if (availablePipelines.size() != 0) { - containerInfo = selectContainer(availablePipelines, size, owner, - excludeList); - } - if (containerInfo != null) { - return containerInfo; - } - } finally { - pipelineManager.releaseReadLock(); - } + ContainerInfo containerInfo = + getContainer(repConfig, owner, excludeList, req); + if (containerInfo != null) { + return containerInfo; + } - if (availablePipelines.size() == 0) { + try { + // TODO: #CLUTIL Remove creation logic when all replication types + // and factors are handled by pipeline creator + Pipeline pipeline = pipelineManager.createPipeline(repConfig); + + // wait until pipeline is ready + pipelineManager.waitPipelineReady(pipeline.getId(), 0); + + } catch (SCMException se) { + LOG.warn("Pipeline creation failed for repConfig {} " + + "Datanodes may be used up. Try to see if any pipeline is in " + + "ALLOCATED state, and then will wait for it to be OPEN", + repConfig, se); + List allocatedPipelines = findPipelinesByState(repConfig, + excludeList, + Pipeline.PipelineState.ALLOCATED); + if (!allocatedPipelines.isEmpty()) { + List allocatedPipelineIDs = + allocatedPipelines.stream() + .map(p -> p.getId()) + .collect(Collectors.toList()); try { - // TODO: #CLUTIL Remove creation logic when all replication types - // and factors are handled by pipeline creator - pipeline = pipelineManager.createPipeline(repConfig); - - // wait until pipeline is ready - pipelineManager.waitPipelineReady(pipeline.getId(), 0); - - } catch (SCMException se) { - LOG.warn("Pipeline creation failed for repConfig {} " + - "Datanodes may be used up. Try to see if any pipeline is in " + - "ALLOCATED state, and then will wait for it to be OPEN", - repConfig, se); - List allocatedPipelines = findPipelinesByState(repConfig, - excludeList, - Pipeline.PipelineState.ALLOCATED); - if (!allocatedPipelines.isEmpty()) { - List allocatedPipelineIDs = - allocatedPipelines.stream() - .map(p -> p.getId()) - .collect(Collectors.toList()); - try { - pipelineManager - .waitOnePipelineReady(allocatedPipelineIDs, 0); - } catch (IOException e) { - LOG.warn("Waiting for one of pipelines {} to be OPEN failed. ", - allocatedPipelineIDs, e); - failureReason = "Waiting for one of pipelines to be OPEN failed. " - + e.getMessage(); - } - } else { - failureReason = se.getMessage(); - } + pipelineManager + .waitOnePipelineReady(allocatedPipelineIDs, 0); } catch (IOException e) { - LOG.warn("Pipeline creation failed for repConfig: {}. " - + "Retrying get pipelines call once.", repConfig, e); - failureReason = e.getMessage(); - } - - pipelineManager.acquireReadLock(); - try { - // If Exception occurred or successful creation of pipeline do one - // final try to fetch pipelines. - availablePipelines = findPipelinesByState(repConfig, - excludeList, - Pipeline.PipelineState.OPEN); - if (availablePipelines.size() == 0) { - LOG.info("Could not find available pipeline of repConfig: {} " - + "even after retrying", repConfig); - break; - } - containerInfo = selectContainer(availablePipelines, size, owner, - excludeList); - if (containerInfo != null) { - return containerInfo; - } - } finally { - pipelineManager.releaseReadLock(); + LOG.warn("Waiting for one of pipelines {} to be OPEN failed. ", + allocatedPipelineIDs, e); + failureReason = "Waiting for one of pipelines to be OPEN failed. " + + e.getMessage(); } + } else { + failureReason = se.getMessage(); } + } catch (IOException e) { + LOG.warn("Pipeline creation failed for repConfig: {}. " + + "Retrying get pipelines call once.", repConfig, e); + failureReason = e.getMessage(); + } + + // If Exception occurred or successful creation of pipeline do one + // final try to fetch pipelines. + containerInfo = getContainer(repConfig, owner, excludeList, req); + if (containerInfo != null) { + return containerInfo; } // we have tried all strategies we know but somehow we are not able @@ -182,6 +150,22 @@ public ContainerInfo getContainer(final long size, + ", replicationConfig: " + repConfig + ". " + failureReason); } + @Nullable + private ContainerInfo getContainer(ReplicationConfig repConfig, String owner, + ExcludeList excludeList, PipelineRequestInformation req) { + // Acquire pipeline manager lock, to avoid any updates to pipeline + // while allocate container happens. This is to avoid scenario like + // mentioned in HDDS-5655. + pipelineManager.acquireReadLock(); + try { + List availablePipelines = findPipelinesByState(repConfig, + excludeList, Pipeline.PipelineState.OPEN); + return selectContainer(availablePipelines, req, owner, excludeList); + } finally { + pipelineManager.releaseReadLock(); + } + } + private List findPipelinesByState( final ReplicationConfig repConfig, final ExcludeList excludeList, @@ -197,23 +181,26 @@ private List findPipelinesByState( return pipelines; } - private ContainerInfo selectContainer(List availablePipelines, - long size, String owner, ExcludeList excludeList) { - Pipeline pipeline; - ContainerInfo containerInfo; + private @Nullable ContainerInfo selectContainer( + List availablePipelines, PipelineRequestInformation req, + String owner, ExcludeList excludeList) { - PipelineRequestInformation pri = - PipelineRequestInformation.Builder.getBuilder().setSize(size) - .build(); - pipeline = pipelineChoosePolicy.choosePipeline( - availablePipelines, pri); + while (!availablePipelines.isEmpty()) { + Pipeline pipeline = pipelineChoosePolicy.choosePipeline( + availablePipelines, req); - // look for OPEN containers that match the criteria. - containerInfo = containerManager.getMatchingContainer(size, owner, - pipeline, excludeList.getContainerIds()); + // look for OPEN containers that match the criteria. + final ContainerInfo containerInfo = containerManager.getMatchingContainer( + req.getSize(), owner, pipeline, excludeList.getContainerIds()); - return containerInfo; + if (containerInfo != null) { + return containerInfo; + } + + availablePipelines.remove(pipeline); + } + return null; } } diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/ContainerReportQueue.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/ContainerReportQueue.java index b08b525a86c5..bffddff87b33 100644 --- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/ContainerReportQueue.java +++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/ContainerReportQueue.java @@ -112,6 +112,9 @@ private boolean addIncrementalReport(ContainerReport val) { // 2. Add ICR report or merge to previous ICR List dataList = dataMap.get(uuidString); + if (mergeIcr(val, dataList)) { + return true; + } dataList.add(val); ++capacity; orderingQueue.add(uuidString); @@ -375,4 +378,9 @@ public int getAndResetDropCount(String type) { } return 0; } + + protected boolean mergeIcr(ContainerReport val, + List dataList) { + return false; + } } diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMDatanodeHeartbeatDispatcher.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMDatanodeHeartbeatDispatcher.java index aaadbbbcb955..38db618ef539 100644 --- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMDatanodeHeartbeatDispatcher.java +++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMDatanodeHeartbeatDispatcher.java @@ -279,6 +279,7 @@ public LayoutReportFromDatanode(DatanodeDetails datanodeDetails, public interface ContainerReport { DatanodeDetails getDatanodeDetails(); ContainerReportType getType(); + void mergeReport(ContainerReport val); } /** @@ -334,6 +335,9 @@ public String getEventId() { return getDatanodeDetails().toString() + ", {type: " + getType() + ", size: " + getReport().getReportsList().size() + "}"; } + + @Override + public void mergeReport(ContainerReport nextReport) { } } /** @@ -374,6 +378,15 @@ public String getEventId() { return getDatanodeDetails().toString() + ", {type: " + getType() + ", size: " + getReport().getReportList().size() + "}"; } + + @Override + public void mergeReport(ContainerReport nextReport) { + if (nextReport.getType() == ContainerReportType.ICR) { + getReport().getReportList().addAll( + ((ReportFromDatanode) nextReport) + .getReport().getReportList()); + } + } } /** diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/StorageContainerManager.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/StorageContainerManager.java index 722244d4c13f..c000514ed33c 100644 --- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/StorageContainerManager.java +++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/StorageContainerManager.java @@ -39,6 +39,7 @@ import org.apache.hadoop.hdds.conf.ReconfigurationHandler; import org.apache.hadoop.hdds.protocol.proto.HddsProtos; import org.apache.hadoop.hdds.protocol.proto.HddsProtos.NodeState; +import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.SCMCommandProto; import org.apache.hadoop.hdds.protocolPB.SCMSecurityProtocolClientSideTranslatorPB; import org.apache.hadoop.hdds.scm.PipelineChoosePolicy; import org.apache.hadoop.hdds.scm.PlacementPolicy; @@ -577,6 +578,9 @@ private void initializeEventHandlers() { eventQueue.addHandler(SCMEvents.PIPELINE_REPORT, pipelineReportHandler); eventQueue.addHandler(SCMEvents.CRL_STATUS_REPORT, crlStatusReportHandler); + scmNodeManager.registerSendCommandNotify( + SCMCommandProto.Type.deleteBlocksCommand, + scmBlockManager.getDeletedBlockLog()::onSent); } private void initializeCertificateClient() throws IOException { diff --git a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/TestSCMCommonPlacementPolicy.java b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/TestSCMCommonPlacementPolicy.java index ffefc7c5f5db..87497a9f0709 100644 --- a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/TestSCMCommonPlacementPolicy.java +++ b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/TestSCMCommonPlacementPolicy.java @@ -24,30 +24,40 @@ import org.apache.hadoop.hdds.conf.ConfigurationSource; import org.apache.hadoop.hdds.conf.OzoneConfiguration; import org.apache.hadoop.hdds.protocol.DatanodeDetails; +import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos; import org.apache.hadoop.hdds.scm.container.ContainerID; import org.apache.hadoop.hdds.scm.container.ContainerReplica; import org.apache.hadoop.hdds.scm.container.MockNodeManager; import org.apache.hadoop.hdds.scm.exceptions.SCMException; import org.apache.hadoop.hdds.scm.net.Node; +import org.apache.hadoop.hdds.scm.node.DatanodeInfo; import org.apache.hadoop.hdds.scm.node.NodeManager; +import org.apache.hadoop.hdds.scm.node.NodeStatus; import org.apache.hadoop.ozone.container.common.SCMTestUtils; import org.apache.ozone.test.GenericTestUtils; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; - import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; import java.util.stream.IntStream; import static org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.ContainerReplicaProto.State.CLOSED; +import static org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.StorageTypeProto.DISK; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import java.util.function.Function; import java.util.stream.Stream; @@ -448,11 +458,66 @@ protected List chooseDatanodesInternal( } }; dummyPlacementPolicy.chooseDatanodes(null, null, 1, 1, 1); - Assertions.assertFalse(usedNodesIdentity.get()); + assertFalse(usedNodesIdentity.get()); dummyPlacementPolicy.chooseDatanodes(null, null, null, 1, 1, 1); Assertions.assertTrue(usedNodesIdentity.get()); } + @Test + public void testDatanodeIsInvalidInCaseOfIncreasingCommittedBytes() { + NodeManager nodeMngr = mock(NodeManager.class); + UUID datanodeUuid = UUID.randomUUID(); + DummyPlacementPolicy placementPolicy = + new DummyPlacementPolicy(nodeMngr, conf, 1); + DatanodeDetails datanodeDetails = mock(DatanodeDetails.class); + when(datanodeDetails.getUuid()).thenReturn(datanodeUuid); + + DatanodeInfo datanodeInfo = mock(DatanodeInfo.class); + NodeStatus nodeStatus = mock(NodeStatus.class); + when(nodeStatus.isNodeWritable()).thenReturn(true); + when(datanodeInfo.getNodeStatus()).thenReturn(nodeStatus); + when(nodeMngr.getNodeByUuid(eq(datanodeUuid))).thenReturn(datanodeInfo); + + // capacity = 200000, used = 90000, remaining = 101000, committed = 500 + StorageContainerDatanodeProtocolProtos.StorageReportProto storageReport1 = + HddsTestUtils.createStorageReport(UUID.randomUUID(), "/data/hdds", + 200000, 90000, 101000, DISK).toBuilder() + .setCommitted(500) + .setFreeSpaceToSpare(10000) + .build(); + // capacity = 200000, used = 90000, remaining = 101000, committed = 1000 + StorageContainerDatanodeProtocolProtos.StorageReportProto storageReport2 = + HddsTestUtils.createStorageReport(UUID.randomUUID(), "/data/hdds", + 200000, 90000, 101000, DISK).toBuilder() + .setCommitted(1000) + .setFreeSpaceToSpare(100000) + .build(); + StorageContainerDatanodeProtocolProtos.MetadataStorageReportProto + metaReport = HddsTestUtils.createMetadataStorageReport("/data/metadata", + 200); + when(datanodeInfo.getStorageReports()) + .thenReturn(Collections.singletonList(storageReport1)) + .thenReturn(Collections.singletonList(storageReport2)); + when(datanodeInfo.getMetadataStorageReports()) + .thenReturn(Collections.singletonList(metaReport)); + + + // 500 committed bytes: + // + // 101000 500 + // | | + // (remaining - committed) > Math.max(4000, freeSpaceToSpare) + // | + // 100000 + // + // Summary: 101000 - 500 > 100000 == true + assertTrue(placementPolicy.isValidNode(datanodeDetails, 100, 4000)); + + // 1000 committed bytes: + // Summary: 101000 - 1000 > 100000 == false + assertFalse(placementPolicy.isValidNode(datanodeDetails, 100, 4000)); + } + private static class DummyPlacementPolicy extends SCMCommonPlacementPolicy { private Map rackMap; private List racks; @@ -485,7 +550,7 @@ private static class DummyPlacementPolicy extends SCMCommonPlacementPolicy { super(nodeManager, conf); this.rackCnt = rackCnt; this.racks = IntStream.range(0, rackCnt) - .mapToObj(i -> Mockito.mock(Node.class)).collect(Collectors.toList()); + .mapToObj(i -> mock(Node.class)).collect(Collectors.toList()); List datanodeDetails = nodeManager.getAllNodes(); rackMap = datanodeRackMap.entrySet().stream() .collect(Collectors.toMap( diff --git a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/block/TestDeletedBlockLog.java b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/block/TestDeletedBlockLog.java index e1d15146d01d..987b1ddbb90d 100644 --- a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/block/TestDeletedBlockLog.java +++ b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/block/TestDeletedBlockLog.java @@ -22,6 +22,8 @@ import org.apache.hadoop.hdds.client.RatisReplicationConfig; import org.apache.hadoop.hdds.client.StandaloneReplicationConfig; import org.apache.hadoop.hdds.protocol.proto.HddsProtos; +import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos; +import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.SCMCommandProto.Type; import org.apache.hadoop.hdds.protocol.proto .StorageContainerDatanodeProtocolProtos.ContainerReplicaProto; import org.apache.hadoop.hdds.scm.ScmConfigKeys; @@ -49,6 +51,9 @@ .DeleteBlockTransactionResult; import org.apache.hadoop.hdds.utils.db.Table; import org.apache.hadoop.hdds.utils.db.TableIterator; +import org.apache.hadoop.ozone.protocol.commands.CommandStatus; +import org.apache.hadoop.ozone.protocol.commands.DeleteBlocksCommand; +import org.apache.hadoop.ozone.protocol.commands.SCMCommand; import org.apache.ozone.test.GenericTestUtils; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; @@ -62,6 +67,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -71,6 +77,7 @@ import java.util.UUID; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiConsumer; import java.util.stream.Collectors; import static org.apache.hadoop.hdds.scm.ScmConfigKeys @@ -255,7 +262,7 @@ private void commitTransactions( List transactionResults, DatanodeDetails... dns) throws IOException { for (DatanodeDetails dnDetails : dns) { - deletedBlockLog + deletedBlockLog.getSCMDeletedBlockTransactionStatusManager() .commitTransactions(transactionResults, dnDetails.getUuid()); } scmHADBTransactionBuffer.flush(); @@ -284,15 +291,6 @@ private void commitTransactions( .collect(Collectors.toList())); } - private void commitTransactions(DatanodeDeletedBlockTransactions - transactions) { - transactions.getDatanodeTransactionMap().forEach((uuid, - deletedBlocksTransactions) -> deletedBlockLog - .commitTransactions(deletedBlocksTransactions.stream() - .map(this::createDeleteBlockTransactionResult) - .collect(Collectors.toList()), uuid)); - } - private DeleteBlockTransactionResult createDeleteBlockTransactionResult( DeletedBlocksTransaction transaction) { return DeleteBlockTransactionResult.newBuilder() @@ -315,6 +313,13 @@ private List getTransactions( transactions.getDatanodeTransactionMap().get(dn.getUuid())) .orElseGet(LinkedList::new)); } + // Simulated transactions are sent + for (Map.Entry> entry : + transactions.getDatanodeTransactionMap().entrySet()) { + DeleteBlocksCommand command = new DeleteBlocksCommand(entry.getValue()); + recordScmCommandToStatusManager(entry.getKey(), command); + sendSCMDeleteBlocksCommand(entry.getKey(), command); + } return txns; } @@ -431,6 +436,9 @@ public void testResetCount() throws Exception { } // Increment for the reset transactions. + // Lets the SCM delete the transaction and wait for the DN reply + // to timeout, thus allowing the transaction to resend the + deletedBlockLog.setScmCommandTimeoutMs(-1L); incrementCount(txIDs); blocks = getAllTransactions(); for (DeletedBlocksTransaction block : blocks) { @@ -442,6 +450,7 @@ public void testResetCount() throws Exception { @Test public void testCommitTransactions() throws Exception { + deletedBlockLog.setScmCommandTimeoutMs(Long.MAX_VALUE); addTransactions(generateData(50), true); mockContainerHealthResult(true); List blocks = @@ -458,6 +467,12 @@ public void testCommitTransactions() throws Exception { DatanodeDetails.newBuilder().setUuid(UUID.randomUUID()) .build()); + blocks = getTransactions(50 * BLOCKS_PER_TXN * THREE); + // SCM will not repeat a transaction until it has timed out. + Assertions.assertEquals(0, blocks.size()); + // Lets the SCM delete the transaction and wait for the DN reply + // to timeout, thus allowing the transaction to resend the + deletedBlockLog.setScmCommandTimeoutMs(-1L); blocks = getTransactions(50 * BLOCKS_PER_TXN * THREE); // only uncommitted dn have transactions Assertions.assertEquals(30, blocks.size()); @@ -467,6 +482,173 @@ public void testCommitTransactions() throws Exception { Assertions.assertEquals(0, blocks.size()); } + private void recordScmCommandToStatusManager( + UUID dnId, DeleteBlocksCommand command) { + Set dnTxSet = command.blocksTobeDeleted() + .stream().map(DeletedBlocksTransaction::getTxID) + .collect(Collectors.toSet()); + deletedBlockLog.recordTransactionCreated(dnId, command.getId(), dnTxSet); + } + + private void sendSCMDeleteBlocksCommand(UUID dnId, SCMCommand scmCommand) { + deletedBlockLog.onSent( + DatanodeDetails.newBuilder().setUuid(dnId).build(), scmCommand); + } + + private void assertNoDuplicateTransactions( + DatanodeDeletedBlockTransactions transactions1, + DatanodeDeletedBlockTransactions transactions2) { + Map> map1 = + transactions1.getDatanodeTransactionMap(); + Map> map2 = + transactions2.getDatanodeTransactionMap(); + + for (Map.Entry> entry : + map1.entrySet()) { + UUID dnId = entry.getKey(); + Set txSet1 = new HashSet<>(entry.getValue()); + Set txSet2 = new HashSet<>(map2.get(dnId)); + + txSet1.retainAll(txSet2); + Assertions.assertEquals(0, txSet1.size(), + String.format("Duplicate Transactions found first transactions %s " + + "second transactions %s for Dn %s", txSet1, txSet2, dnId)); + } + } + + + private void assertContainsAllTransactions( + DatanodeDeletedBlockTransactions transactions1, + DatanodeDeletedBlockTransactions transactions2) { + Map> map1 = + transactions1.getDatanodeTransactionMap(); + Map> map2 = + transactions2.getDatanodeTransactionMap(); + + for (Map.Entry> entry : + map1.entrySet()) { + UUID dnId = entry.getKey(); + Set txSet1 = new HashSet<>(entry.getValue()); + Set txSet2 = new HashSet<>(map2.get(dnId)); + + Assertions.assertTrue(txSet1.containsAll(txSet2)); + } + } + + private void commitSCMCommandStatus(Long scmCmdId, UUID dnID, + StorageContainerDatanodeProtocolProtos.CommandStatus.Status status) { + List deleteBlockStatus = new ArrayList<>(); + deleteBlockStatus.add(CommandStatus.CommandStatusBuilder.newBuilder() + .setCmdId(scmCmdId) + .setType(Type.deleteBlocksCommand) + .setStatus(status) + .build() + .getProtoBufMessage()); + + deletedBlockLog.getSCMDeletedBlockTransactionStatusManager() + .commitSCMCommandStatus(deleteBlockStatus, dnID); + } + + private void createDeleteBlocksCommandAndAction( + DatanodeDeletedBlockTransactions transactions, + BiConsumer afterCreate) { + for (Map.Entry> entry : + transactions.getDatanodeTransactionMap().entrySet()) { + UUID dnId = entry.getKey(); + List dnTXs = entry.getValue(); + DeleteBlocksCommand command = new DeleteBlocksCommand(dnTXs); + afterCreate.accept(dnId, command); + } + } + + @Test + public void testNoDuplicateTransactionsForInProcessingSCMCommand() + throws Exception { + // The SCM will not resend these transactions in blow case: + // - If the command has not been sent; + // - The DN does not report the status of the command via heartbeat + // After the command is sent; + // - If the DN reports the command status as PENDING; + addTransactions(generateData(10), true); + int blockLimit = 2 * BLOCKS_PER_TXN * THREE; + mockContainerHealthResult(true); + + // If the command has not been sent + DatanodeDeletedBlockTransactions transactions1 = + deletedBlockLog.getTransactions(blockLimit, new HashSet<>(dnList)); + createDeleteBlocksCommandAndAction(transactions1, + this::recordScmCommandToStatusManager); + + // - The DN does not report the status of the command via heartbeat + // After the command is sent + DatanodeDeletedBlockTransactions transactions2 = + deletedBlockLog.getTransactions(blockLimit, new HashSet<>(dnList)); + assertNoDuplicateTransactions(transactions1, transactions2); + createDeleteBlocksCommandAndAction(transactions2, (dnId, command) -> { + recordScmCommandToStatusManager(dnId, command); + sendSCMDeleteBlocksCommand(dnId, command); + }); + + // - If the DN reports the command status as PENDING + DatanodeDeletedBlockTransactions transactions3 = + deletedBlockLog.getTransactions(blockLimit, new HashSet<>(dnList)); + assertNoDuplicateTransactions(transactions1, transactions3); + createDeleteBlocksCommandAndAction(transactions3, (dnId, command) -> { + recordScmCommandToStatusManager(dnId, command); + sendSCMDeleteBlocksCommand(dnId, command); + commitSCMCommandStatus(command.getId(), dnId, + StorageContainerDatanodeProtocolProtos.CommandStatus.Status.PENDING); + }); + assertNoDuplicateTransactions(transactions3, transactions1); + assertNoDuplicateTransactions(transactions3, transactions2); + + DatanodeDeletedBlockTransactions transactions4 = + deletedBlockLog.getTransactions(blockLimit, new HashSet<>(dnList)); + assertNoDuplicateTransactions(transactions4, transactions1); + assertNoDuplicateTransactions(transactions4, transactions2); + assertNoDuplicateTransactions(transactions4, transactions3); + } + + @Test + public void testFailedAndTimeoutSCMCommandCanBeResend() throws Exception { + // The SCM will be resent these transactions in blow case: + // - Executed failed commands; + // - DN does not refresh the PENDING state for more than a period of time; + deletedBlockLog.setScmCommandTimeoutMs(Long.MAX_VALUE); + addTransactions(generateData(10), true); + int blockLimit = 2 * BLOCKS_PER_TXN * THREE; + mockContainerHealthResult(true); + + // - DN does not refresh the PENDING state for more than a period of time; + DatanodeDeletedBlockTransactions transactions = + deletedBlockLog.getTransactions(blockLimit, new HashSet<>(dnList)); + createDeleteBlocksCommandAndAction(transactions, (dnId, command) -> { + recordScmCommandToStatusManager(dnId, command); + sendSCMDeleteBlocksCommand(dnId, command); + commitSCMCommandStatus(command.getId(), dnId, + StorageContainerDatanodeProtocolProtos.CommandStatus.Status.PENDING); + }); + + // - Executed failed commands; + DatanodeDeletedBlockTransactions transactions2 = + deletedBlockLog.getTransactions(blockLimit, new HashSet<>(dnList)); + createDeleteBlocksCommandAndAction(transactions2, (dnId, command) -> { + recordScmCommandToStatusManager(dnId, command); + sendSCMDeleteBlocksCommand(dnId, command); + commitSCMCommandStatus(command.getId(), dnId, + StorageContainerDatanodeProtocolProtos.CommandStatus.Status.FAILED); + }); + + deletedBlockLog.setScmCommandTimeoutMs(-1L); + DatanodeDeletedBlockTransactions transactions3 = + deletedBlockLog.getTransactions(Integer.MAX_VALUE, + new HashSet<>(dnList)); + assertNoDuplicateTransactions(transactions, transactions2); + assertContainsAllTransactions(transactions3, transactions); + assertContainsAllTransactions(transactions3, transactions2); + } + @Test public void testDNOnlyOneNodeHealthy() throws Exception { Map> deletedBlocks = generateData(50); @@ -496,9 +678,7 @@ public void testInadequateReplicaCommit() throws Exception { // For the first 30 txn, deletedBlockLog only has the txn from dn1 and dn2 // For the rest txn, txn will be got from all dns. // Committed txn will be: 1-40. 1-40. 31-40 - commitTransactions(deletedBlockLog.getTransactions( - 30 * BLOCKS_PER_TXN * THREE, - dnList.stream().collect(Collectors.toSet()))); + commitTransactions(getTransactions(30 * BLOCKS_PER_TXN * THREE)); // The rest txn shall be: 41-50. 41-50. 41-50 List blocks = getAllTransactions(); @@ -590,6 +770,7 @@ public void testPersistence() throws Exception { @Test public void testDeletedBlockTransactions() throws IOException, TimeoutException { + deletedBlockLog.setScmCommandTimeoutMs(Long.MAX_VALUE); mockContainerHealthResult(true); int txNum = 10; List blocks; @@ -622,9 +803,21 @@ public void testDeletedBlockTransactions() // add two transactions for same container containerID = blocks.get(0).getContainerID(); Map> deletedBlocksMap = new HashMap<>(); - deletedBlocksMap.put(containerID, new LinkedList<>()); + Random random = new Random(); + long localId = random.nextLong(); + deletedBlocksMap.put(containerID, new LinkedList<>( + Collections.singletonList(localId))); addTransactions(deletedBlocksMap, true); + blocks = getTransactions(txNum * BLOCKS_PER_TXN * ONE); + // Only newly added Blocks will be sent, as previously sent transactions + // that have not yet timed out will not be sent. + Assertions.assertEquals(1, blocks.size()); + Assertions.assertEquals(1, blocks.get(0).getLocalIDCount()); + Assertions.assertEquals(blocks.get(0).getLocalID(0), localId); + // Lets the SCM delete the transaction and wait for the DN reply + // to timeout, thus allowing the transaction to resend the + deletedBlockLog.setScmCommandTimeoutMs(-1L); // get should return two transactions for the same container blocks = getTransactions(txNum * BLOCKS_PER_TXN * ONE); Assertions.assertEquals(2, blocks.size()); diff --git a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/block/TestSCMBlockDeletingService.java b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/block/TestSCMBlockDeletingService.java new file mode 100644 index 000000000000..3bd7ad00f6a8 --- /dev/null +++ b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/block/TestSCMBlockDeletingService.java @@ -0,0 +1,177 @@ +/** + * 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.hdds.scm.block; + +import org.apache.hadoop.hdds.conf.OzoneConfiguration; +import org.apache.hadoop.hdds.conf.ReconfigurationHandler; +import org.apache.hadoop.hdds.protocol.DatanodeDetails; +import org.apache.hadoop.hdds.protocol.MockDatanodeDetails; +import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.SCMCommandProto.Type; +import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.DeletedBlocksTransaction; +import org.apache.hadoop.hdds.scm.ScmConfig; +import org.apache.hadoop.hdds.scm.events.SCMEvents; +import org.apache.hadoop.hdds.scm.ha.SCMContext; +import org.apache.hadoop.hdds.scm.ha.SCMServiceManager; +import org.apache.hadoop.hdds.scm.node.NodeManager; +import org.apache.hadoop.hdds.scm.node.NodeStatus; +import org.apache.hadoop.hdds.server.events.EventPublisher; +import org.apache.hadoop.ozone.container.common.statemachine.DatanodeConfiguration; +import org.apache.hadoop.ozone.protocol.commands.CommandForDatanode; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import java.time.Clock; +import java.time.ZoneOffset; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anySet; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Test SCMBlockDeletingService. + */ +public class TestSCMBlockDeletingService { + private SCMBlockDeletingService service; + private EventPublisher eventPublisher; + private List datanodeDetails; + private OzoneConfiguration conf; + private NodeManager nodeManager; + private ScmBlockDeletingServiceMetrics metrics; + + @BeforeEach + public void setup() throws Exception { + nodeManager = mock(NodeManager.class); + eventPublisher = mock(EventPublisher.class); + conf = new OzoneConfiguration(); + metrics = ScmBlockDeletingServiceMetrics.create(); + when(nodeManager.getTotalDatanodeCommandCount(any(), + any())).thenReturn(0); + SCMServiceManager scmServiceManager = mock(SCMServiceManager.class); + SCMContext scmContext = mock(SCMContext.class); + + DatanodeDeletedBlockTransactions ddbt = + new DatanodeDeletedBlockTransactions(); + DatanodeDetails datanode1 = MockDatanodeDetails.randomDatanodeDetails(); + DatanodeDetails datanode2 = MockDatanodeDetails.randomDatanodeDetails(); + DatanodeDetails datanode3 = MockDatanodeDetails.randomDatanodeDetails(); + datanodeDetails = Arrays.asList(datanode1, datanode2, datanode3); + when(nodeManager.getNodes(NodeStatus.inServiceHealthy())).thenReturn( + datanodeDetails); + DeletedBlocksTransaction tx1 = createTestDeleteTxn(1, Arrays.asList(1L), 1); + ddbt.addTransactionToDN(datanode1.getUuid(), tx1); + ddbt.addTransactionToDN(datanode2.getUuid(), tx1); + ddbt.addTransactionToDN(datanode3.getUuid(), tx1); + DeletedBlockLog mockDeletedBlockLog = mock(DeletedBlockLog.class); + when(mockDeletedBlockLog.getTransactions( + anyInt(), anySet())).thenReturn(ddbt); + + service = spy(new SCMBlockDeletingService( + mockDeletedBlockLog, nodeManager, eventPublisher, scmContext, + scmServiceManager, conf, conf.getObject(ScmConfig.class), metrics, Clock.system( + ZoneOffset.UTC), mock(ReconfigurationHandler.class))); + when(service.shouldRun()).thenReturn(true); + } + + @AfterEach + public void stop() { + service.stop(); + ScmBlockDeletingServiceMetrics.unRegister(); + } + + @Test + public void testCall() throws Exception { + callDeletedBlockTransactionScanner(); + + ArgumentCaptor argumentCaptor = + ArgumentCaptor.forClass(CommandForDatanode.class); + + // Three Datanode is healthy and in-service, and the task queue is empty, + // so the transaction will send to all three Datanode + verify(eventPublisher, times(3)).fireEvent( + eq(SCMEvents.DATANODE_COMMAND), argumentCaptor.capture()); + List actualCommands = argumentCaptor.getAllValues(); + List actualDnIds = actualCommands.stream() + .map(CommandForDatanode::getDatanodeId) + .collect(Collectors.toList()); + Set expectedDnIdsSet = datanodeDetails.stream() + .map(DatanodeDetails::getUuid).collect(Collectors.toSet()); + + assertEquals(expectedDnIdsSet, new HashSet<>(actualDnIds)); + assertEquals(datanodeDetails.size(), + metrics.getNumBlockDeletionCommandSent()); + // Echo Command has one Transaction + assertEquals(datanodeDetails.size() * 1, + metrics.getNumBlockDeletionTransactionSent()); + } + + private void callDeletedBlockTransactionScanner() throws Exception { + service.getTasks().poll().call(); + } + + @Test + public void testLimitCommandSending() throws Exception { + DatanodeConfiguration dnConf = + conf.getObject(DatanodeConfiguration.class); + int pendingCommandLimit = dnConf.getBlockDeleteQueueLimit(); + + // The number of commands pending on all Datanodes has reached the limit. + when(nodeManager.getTotalDatanodeCommandCount(any(), + any())).thenReturn(pendingCommandLimit); + assertEquals(0, + service.getDatanodesWithinCommandLimit(datanodeDetails).size()); + + // The number of commands pending on all Datanodes is 0 + when(nodeManager.getTotalDatanodeCommandCount(any(), + any())).thenReturn(0); + assertEquals(datanodeDetails.size(), + service.getDatanodesWithinCommandLimit(datanodeDetails).size()); + + // The number of commands pending on first Datanodes has reached the limit. + DatanodeDetails fullDatanode = datanodeDetails.get(0); + when(nodeManager.getTotalDatanodeCommandCount(fullDatanode, + Type.deleteBlocksCommand)).thenReturn(pendingCommandLimit); + Set includeNodes = + service.getDatanodesWithinCommandLimit(datanodeDetails); + assertEquals(datanodeDetails.size() - 1, + includeNodes.size()); + assertFalse(includeNodes.contains(fullDatanode)); + } + + private DeletedBlocksTransaction createTestDeleteTxn( + long txnID, List blocks, long containerID) { + return DeletedBlocksTransaction.newBuilder().setTxID(txnID) + .setContainerID(containerID).addAllLocalID(blocks).setCount(0).build(); + } +} diff --git a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/block/TestSCMDeleteBlocksCommandStatusManager.java b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/block/TestSCMDeleteBlocksCommandStatusManager.java new file mode 100644 index 000000000000..888cb42fd7de --- /dev/null +++ b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/block/TestSCMDeleteBlocksCommandStatusManager.java @@ -0,0 +1,256 @@ +/** + * 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.hdds.scm.block; + +import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.BeforeEach; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import static org.apache.hadoop.hdds.scm.block.SCMDeletedBlockTransactionStatusManager.SCMDeleteBlocksCommandStatusManager; +import static org.apache.hadoop.hdds.scm.block.SCMDeletedBlockTransactionStatusManager.SCMDeleteBlocksCommandStatusManager.CmdStatus.SENT; +import static org.apache.hadoop.hdds.scm.block.SCMDeletedBlockTransactionStatusManager.SCMDeleteBlocksCommandStatusManager.CmdStatus.TO_BE_SENT; +import static org.apache.hadoop.hdds.scm.block.SCMDeletedBlockTransactionStatusManager.SCMDeleteBlocksCommandStatusManager.CmdStatusData; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * A test for SCMDeleteBlocksCommandStatusManager. + */ +public class TestSCMDeleteBlocksCommandStatusManager { + + private SCMDeleteBlocksCommandStatusManager manager; + private UUID dnId1; + private UUID dnId2; + private long scmCmdId1; + private long scmCmdId2; + private long scmCmdId3; + private long scmCmdId4; + private Set deletedBlocksTxIds1; + private Set deletedBlocksTxIds2; + private Set deletedBlocksTxIds3; + private Set deletedBlocksTxIds4; + + @BeforeEach + public void setup() throws Exception { + manager = new SCMDeleteBlocksCommandStatusManager(); + // Create test data + dnId1 = UUID.randomUUID(); + dnId2 = UUID.randomUUID(); + scmCmdId1 = 1L; + scmCmdId2 = 2L; + scmCmdId3 = 3L; + scmCmdId4 = 4L; + deletedBlocksTxIds1 = new HashSet<>(); + deletedBlocksTxIds1.add(100L); + deletedBlocksTxIds2 = new HashSet<>(); + deletedBlocksTxIds2.add(200L); + deletedBlocksTxIds3 = new HashSet<>(); + deletedBlocksTxIds3.add(300L); + deletedBlocksTxIds4 = new HashSet<>(); + deletedBlocksTxIds4.add(400L); + } + + @Test + public void testRecordScmCommand() { + CmdStatusData statusData = + SCMDeleteBlocksCommandStatusManager.createScmCmdStatusData( + dnId1, scmCmdId1, deletedBlocksTxIds1); + + manager.recordScmCommand(statusData); + + assertNotNull(manager.getScmCmdStatusRecord().get(dnId1)); + assertEquals(1, manager.getScmCmdStatusRecord().get(dnId1).size()); + CmdStatusData cmdStatusData = + manager.getScmCmdStatusRecord().get(dnId1).get(scmCmdId1); + assertNotNull(cmdStatusData); + assertEquals(dnId1, statusData.getDnId()); + assertEquals(scmCmdId1, statusData.getScmCmdId()); + assertEquals(deletedBlocksTxIds1, statusData.getDeletedBlocksTxIds()); + // The default status is `CmdStatus.TO_BE_SENT` + assertEquals(TO_BE_SENT, statusData.getStatus()); + } + + @Test + public void testOnSent() { + CmdStatusData statusData = + SCMDeleteBlocksCommandStatusManager.createScmCmdStatusData( + dnId1, scmCmdId1, deletedBlocksTxIds1); + manager.recordScmCommand(statusData); + + Map dnStatusRecord = + manager.getScmCmdStatusRecord().get(dnId1); + // After the Command is sent by SCM, the status of the Command + // will change from TO_BE_SENT to SENT + assertEquals(TO_BE_SENT, dnStatusRecord.get(scmCmdId1).getStatus()); + manager.onSent(dnId1, scmCmdId1); + assertEquals(SENT, dnStatusRecord.get(scmCmdId1).getStatus()); + } + + @Test + public void testUpdateStatusByDNCommandStatus() { + // Test all Status update by Datanode Heartbeat report. + // SENT -> PENDING_EXECUTED: The DeleteBlocksCommand is sent and received + // by the Datanode, but the command is not executed by the Datanode, + // the command is waiting to be executed. + + // SENT -> NEED_RESEND: The DeleteBlocksCommand is sent and lost before + // it is received by the DN. + // SENT -> EXECUTED: The DeleteBlocksCommand has been sent to Datanode, + // executed by DN, and executed successfully. + // + // PENDING_EXECUTED -> PENDING_EXECUTED: The DeleteBlocksCommand continues + // to wait to be executed by Datanode. + // PENDING_EXECUTED -> NEED_RESEND: The DeleteBlocksCommand waited for a + // while and was executed, but the execution failed; Or the + // DeleteBlocksCommand was lost while waiting(such as the Datanode restart) + // + // PENDING_EXECUTED -> EXECUTED: The Command waits for a period of + // time on the DN and is executed successfully. + + recordAndSentCommand(manager, dnId1, + Arrays.asList(scmCmdId1, scmCmdId2, scmCmdId3, scmCmdId4), + Arrays.asList(deletedBlocksTxIds1, deletedBlocksTxIds2, + deletedBlocksTxIds3, deletedBlocksTxIds4)); + + Map dnStatusRecord = + manager.getScmCmdStatusRecord().get(dnId1); + assertEquals(SENT, dnStatusRecord.get(scmCmdId1).getStatus()); + assertEquals(SENT, dnStatusRecord.get(scmCmdId2).getStatus()); + assertEquals(SENT, dnStatusRecord.get(scmCmdId3).getStatus()); + assertEquals(SENT, dnStatusRecord.get(scmCmdId4).getStatus()); + + // SENT -> PENDING_EXECUTED + manager.updateStatusByDNCommandStatus(dnId1, scmCmdId1, + StorageContainerDatanodeProtocolProtos.CommandStatus.Status.PENDING); + // SENT -> EXECUTED + manager.updateStatusByDNCommandStatus(dnId1, scmCmdId2, + StorageContainerDatanodeProtocolProtos.CommandStatus.Status.EXECUTED); + // SENT -> NEED_RESEND + manager.updateStatusByDNCommandStatus(dnId1, scmCmdId3, + StorageContainerDatanodeProtocolProtos.CommandStatus.Status.FAILED); + // SENT -> PENDING_EXECUTED + manager.updateStatusByDNCommandStatus(dnId1, scmCmdId4, + StorageContainerDatanodeProtocolProtos.CommandStatus.Status.PENDING); + + assertEquals(SENT, dnStatusRecord.get(scmCmdId1).getStatus()); + assertNull(dnStatusRecord.get(scmCmdId2)); + assertNull(dnStatusRecord.get(scmCmdId3)); + assertEquals(SENT, dnStatusRecord.get(scmCmdId4).getStatus()); + } + + @Test + public void testCleanSCMCommandForDn() { + // Transactions in states EXECUTED and NEED_RESEND will be cleaned up + // directly, while transactions in states PENDING_EXECUTED and SENT + // will be cleaned up after timeout + recordAndSentCommand(manager, dnId1, + Arrays.asList(scmCmdId1, scmCmdId2, scmCmdId3, scmCmdId4), + Arrays.asList(deletedBlocksTxIds1, deletedBlocksTxIds2, + deletedBlocksTxIds3, deletedBlocksTxIds4)); + + // SENT -> PENDING_EXECUTED + manager.updateStatusByDNCommandStatus(dnId1, scmCmdId1, + StorageContainerDatanodeProtocolProtos.CommandStatus.Status.PENDING); + // SENT -> EXECUTED + manager.updateStatusByDNCommandStatus(dnId1, scmCmdId2, + StorageContainerDatanodeProtocolProtos.CommandStatus.Status.EXECUTED); + // SENT -> NEED_RESEND + manager.updateStatusByDNCommandStatus(dnId1, scmCmdId3, + StorageContainerDatanodeProtocolProtos.CommandStatus.Status.FAILED); + + Map dnStatusRecord = + manager.getScmCmdStatusRecord().get(dnId1); + assertNotNull(dnStatusRecord.get(scmCmdId1)); + assertNull(dnStatusRecord.get(scmCmdId2)); + assertNull(dnStatusRecord.get(scmCmdId3)); + assertNotNull(dnStatusRecord.get(scmCmdId4)); + + manager.cleanTimeoutSCMCommand(dnId1, Long.MAX_VALUE); + + // scmCmdId1 is PENDING_EXECUTED will be cleaned up after timeout + assertNotNull(dnStatusRecord.get(scmCmdId1)); + assertNull(dnStatusRecord.get(scmCmdId3)); + assertNull(dnStatusRecord.get(scmCmdId2)); + // scmCmdId4 is SENT will be cleaned up after timeout + assertNotNull(dnStatusRecord.get(scmCmdId4)); + + manager.cleanTimeoutSCMCommand(dnId1, -1); + assertNull(dnStatusRecord.get(scmCmdId1)); + assertNull(dnStatusRecord.get(scmCmdId4)); + } + + @Test + public void testCleanAllTimeoutSCMCommand() { + // Test All EXECUTED and NEED_RESEND status in the DN will be cleaned up + + // Transactions in states EXECUTED and NEED_RESEND will be cleaned up + // directly, while transactions in states PENDING_EXECUTED and SENT + // will be cleaned up after timeout + recordAndSentCommand(manager, dnId1, Arrays.asList(scmCmdId1), + Arrays.asList(deletedBlocksTxIds1)); + recordAndSentCommand(manager, dnId2, Arrays.asList(scmCmdId2), + Arrays.asList(deletedBlocksTxIds2)); + + Map dn1StatusRecord = + manager.getScmCmdStatusRecord().get(dnId1); + Map dn2StatusRecord = + manager.getScmCmdStatusRecord().get(dnId2); + + // Only let the scmCmdId1 have a Heartbeat report, its status will be + // updated, the scmCmdId2 still in SENT status. + // SENT -> PENDING_EXECUTED + manager.updateStatusByDNCommandStatus(dnId1, scmCmdId1, + StorageContainerDatanodeProtocolProtos.CommandStatus.Status.PENDING); + + manager.cleanAllTimeoutSCMCommand(Long.MAX_VALUE); + // scmCmdId1 is PENDING_EXECUTED will be cleaned up after timeout + assertNotNull(dn1StatusRecord.get(scmCmdId1)); + assertNotNull(dn2StatusRecord.get(scmCmdId2)); + + // scmCmdId2 is SENT will be cleaned up after timeout + manager.cleanAllTimeoutSCMCommand(-1); + assertNull(dn1StatusRecord.get(scmCmdId1)); + assertNull(dn2StatusRecord.get(scmCmdId2)); + + } + + private void recordAndSentCommand( + SCMDeleteBlocksCommandStatusManager statusManager, + UUID dnId, List scmCmdIds, List> txIds) { + assertEquals(scmCmdIds.size(), txIds.size()); + for (int i = 0; i < scmCmdIds.size(); i++) { + long scmCmdId = scmCmdIds.get(i); + Set deletedBlocksTxIds = txIds.get(i); + CmdStatusData statusData = + SCMDeleteBlocksCommandStatusManager.createScmCmdStatusData( + dnId, scmCmdId, deletedBlocksTxIds); + statusManager.recordScmCommand(statusData); + statusManager.onSent(dnId, scmCmdId); + } + } + +} diff --git a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/MockNodeManager.java b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/MockNodeManager.java index 98638ebe009d..794dedceef06 100644 --- a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/MockNodeManager.java +++ b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/MockNodeManager.java @@ -227,7 +227,7 @@ private void populateNodeMetric(DatanodeDetails datanodeDetails, int x) { NODES[x % NODES.length].capacity - NODES[x % NODES.length].used; newStat.set( (NODES[x % NODES.length].capacity), - (NODES[x % NODES.length].used), remaining); + (NODES[x % NODES.length].used), remaining, 0, 100000); this.nodeMetricMap.put(datanodeDetails, newStat); aggregateStat.add(newStat); diff --git a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/balancer/TestContainerBalancerTask.java b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/balancer/TestContainerBalancerTask.java index 4bc3cf43cf6e..56d02dabb5fa 100644 --- a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/balancer/TestContainerBalancerTask.java +++ b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/balancer/TestContainerBalancerTask.java @@ -1207,7 +1207,8 @@ private double createCluster() { datanodeCapacity = (long) (datanodeUsedSpace / nodeUtilizations.get(i)); } SCMNodeStat stat = new SCMNodeStat(datanodeCapacity, datanodeUsedSpace, - datanodeCapacity - datanodeUsedSpace); + datanodeCapacity - datanodeUsedSpace, 0, + datanodeCapacity - datanodeUsedSpace - 1); nodesInCluster.get(i).setScmNodeStat(stat); clusterUsedSpace += datanodeUsedSpace; clusterCapacity += datanodeCapacity; diff --git a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/balancer/TestFindTargetStrategy.java b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/balancer/TestFindTargetStrategy.java index 7e734042d883..bb6f17bcc105 100644 --- a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/balancer/TestFindTargetStrategy.java +++ b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/balancer/TestFindTargetStrategy.java @@ -56,11 +56,11 @@ public void testFindTargetGreedyByUsage() { //create three datanodes with different usageinfo DatanodeUsageInfo dui1 = new DatanodeUsageInfo(MockDatanodeDetails - .randomDatanodeDetails(), new SCMNodeStat(100, 0, 40)); + .randomDatanodeDetails(), new SCMNodeStat(100, 0, 40, 0, 30)); DatanodeUsageInfo dui2 = new DatanodeUsageInfo(MockDatanodeDetails - .randomDatanodeDetails(), new SCMNodeStat(100, 0, 60)); + .randomDatanodeDetails(), new SCMNodeStat(100, 0, 60, 0, 30)); DatanodeUsageInfo dui3 = new DatanodeUsageInfo(MockDatanodeDetails - .randomDatanodeDetails(), new SCMNodeStat(100, 0, 80)); + .randomDatanodeDetails(), new SCMNodeStat(100, 0, 80, 0, 30)); //insert in ascending order overUtilizedDatanodes.add(dui1); @@ -98,11 +98,11 @@ public void testFindTargetGreedyByUsage() { public void testResetPotentialTargets() { // create three datanodes with different usage infos DatanodeUsageInfo dui1 = new DatanodeUsageInfo(MockDatanodeDetails - .randomDatanodeDetails(), new SCMNodeStat(100, 30, 70)); + .randomDatanodeDetails(), new SCMNodeStat(100, 30, 70, 0, 50)); DatanodeUsageInfo dui2 = new DatanodeUsageInfo(MockDatanodeDetails - .randomDatanodeDetails(), new SCMNodeStat(100, 20, 80)); + .randomDatanodeDetails(), new SCMNodeStat(100, 20, 80, 0, 60)); DatanodeUsageInfo dui3 = new DatanodeUsageInfo(MockDatanodeDetails - .randomDatanodeDetails(), new SCMNodeStat(100, 10, 90)); + .randomDatanodeDetails(), new SCMNodeStat(100, 10, 90, 0, 70)); List potentialTargets = new ArrayList<>(); potentialTargets.add(dui1); @@ -179,18 +179,18 @@ public void testFindTargetGreedyByNetworkTopology() { List overUtilizedDatanodes = new ArrayList<>(); //set the farthest target with the lowest usage info overUtilizedDatanodes.add( - new DatanodeUsageInfo(target5, new SCMNodeStat(100, 0, 90))); + new DatanodeUsageInfo(target5, new SCMNodeStat(100, 0, 90, 0, 80))); //set the tree targets, which have the same network topology distance //to source , with different usage info overUtilizedDatanodes.add( - new DatanodeUsageInfo(target2, new SCMNodeStat(100, 0, 20))); + new DatanodeUsageInfo(target2, new SCMNodeStat(100, 0, 20, 0, 10))); overUtilizedDatanodes.add( - new DatanodeUsageInfo(target3, new SCMNodeStat(100, 0, 40))); + new DatanodeUsageInfo(target3, new SCMNodeStat(100, 0, 40, 0, 30))); overUtilizedDatanodes.add( - new DatanodeUsageInfo(target4, new SCMNodeStat(100, 0, 60))); + new DatanodeUsageInfo(target4, new SCMNodeStat(100, 0, 60, 0, 50))); //set the nearest target with the highest usage info overUtilizedDatanodes.add( - new DatanodeUsageInfo(target1, new SCMNodeStat(100, 0, 10))); + new DatanodeUsageInfo(target1, new SCMNodeStat(100, 0, 10, 0, 5))); FindTargetGreedyByNetworkTopology findTargetGreedyByNetworkTopology = diff --git a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/placement/algorithms/TestSCMContainerPlacementCapacity.java b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/placement/algorithms/TestSCMContainerPlacementCapacity.java index 910fe75ede6c..e51f9731ad4a 100644 --- a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/placement/algorithms/TestSCMContainerPlacementCapacity.java +++ b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/container/placement/algorithms/TestSCMContainerPlacementCapacity.java @@ -103,13 +103,13 @@ public void chooseDatanodes() throws SCMException { .thenReturn(new ArrayList<>(datanodes)); when(mockNodeManager.getNodeStat(any())) - .thenReturn(new SCMNodeMetric(100L, 0L, 100L)); + .thenReturn(new SCMNodeMetric(100L, 0L, 100L, 0, 90)); when(mockNodeManager.getNodeStat(datanodes.get(2))) - .thenReturn(new SCMNodeMetric(100L, 90L, 10L)); + .thenReturn(new SCMNodeMetric(100L, 90L, 10L, 0, 9)); when(mockNodeManager.getNodeStat(datanodes.get(3))) - .thenReturn(new SCMNodeMetric(100L, 80L, 20L)); + .thenReturn(new SCMNodeMetric(100L, 80L, 20L, 0, 19)); when(mockNodeManager.getNodeStat(datanodes.get(4))) - .thenReturn(new SCMNodeMetric(100L, 70L, 30L)); + .thenReturn(new SCMNodeMetric(100L, 70L, 30L, 0, 20)); when(mockNodeManager.getNodeByUuid(any(UUID.class))).thenAnswer( invocation -> datanodes.stream() .filter(dn -> dn.getUuid().equals(invocation.getArgument(0))) diff --git a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/ha/TestSCMHAManagerImpl.java b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/ha/TestSCMHAManagerImpl.java index b1c12cdf71c1..1ab60015efef 100644 --- a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/ha/TestSCMHAManagerImpl.java +++ b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/ha/TestSCMHAManagerImpl.java @@ -17,6 +17,7 @@ package org.apache.hadoop.hdds.scm.ha; +import java.net.InetSocketAddress; import java.nio.file.Path; import java.time.Clock; import java.time.ZoneOffset; @@ -40,21 +41,31 @@ import org.apache.hadoop.hdds.security.x509.certificate.client.CertificateClient; import org.apache.hadoop.hdds.utils.TransactionInfo; import org.apache.hadoop.hdds.utils.db.BatchOperation; +import org.apache.hadoop.hdds.utils.db.DBCheckpoint; import org.apache.hadoop.hdds.utils.db.DBStore; import org.apache.hadoop.hdds.utils.db.Table; +import org.apache.hadoop.net.NetUtils; import org.apache.hadoop.security.authentication.client.AuthenticationException; import org.apache.ozone.test.GenericTestUtils; import org.apache.ratis.server.DivisionInfo; -import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.io.TempDir; import java.io.IOException; +import java.util.List; import java.util.UUID; import java.util.concurrent.TimeoutException; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assumptions.assumeThat; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; @@ -64,27 +75,35 @@ /** * Test cases to verify {@link org.apache.hadoop.hdds.scm.ha.SCMHAManagerImpl}. */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) class TestSCMHAManagerImpl { - @TempDir + private static final String LEADER_SCM_ID = "leader"; + private static final int LEADER_PORT = 9894; + private static final String FOLLOWER_SCM_ID = "follower"; + private Path storageBaseDir; private String clusterID; private SCMHAManager primarySCMHAManager; + private SCMRatisServer follower; - @BeforeEach - void setup() throws IOException, InterruptedException, + @BeforeAll + void setup(@TempDir Path tempDir) throws IOException, InterruptedException, TimeoutException { + storageBaseDir = tempDir; clusterID = UUID.randomUUID().toString(); - OzoneConfiguration conf = getConfig("scm1", 9894); - final StorageContainerManager scm = getMockStorageContainerManager(conf); - SCMRatisServerImpl.initialize(clusterID, scm.getScmId(), - scm.getScmNodeDetails(), conf); - scm.getScmHAManager().start(); + final StorageContainerManager scm = getMockStorageContainerManager(LEADER_SCM_ID, LEADER_PORT); + SCMRatisServerImpl.initialize(clusterID, LEADER_SCM_ID, scm.getScmNodeDetails(), scm.getConfiguration()); primarySCMHAManager = scm.getScmHAManager(); + primarySCMHAManager.start(); final DivisionInfo ratisDivision = primarySCMHAManager.getRatisServer() .getDivision().getInfo(); // Wait for Ratis Server to be ready waitForSCMToBeReady(ratisDivision); + StorageContainerManager followerSCM = getMockStorageContainerManager(FOLLOWER_SCM_ID, 9898); + follower = followerSCM.getScmHAManager() + .getRatisServer(); } private OzoneConfiguration getConfig(String scmId, int ratisPort) { @@ -97,42 +116,61 @@ private OzoneConfiguration getConfig(String scmId, int ratisPort) { return conf; } - public void waitForSCMToBeReady(DivisionInfo ratisDivision) + private void waitForSCMToBeReady(DivisionInfo ratisDivision) throws TimeoutException, InterruptedException { GenericTestUtils.waitFor(ratisDivision::isLeaderReady, 1000, 10000); } - @AfterEach - public void cleanup() throws IOException { + @AfterAll + void cleanup() throws IOException { + follower.stop(); primarySCMHAManager.stop(); } @Test - public void testAddSCM() throws IOException, InterruptedException { - Assertions.assertEquals(1, primarySCMHAManager.getRatisServer() - .getDivision().getGroup().getPeers().size()); + @Order(1) + void testAddSCM() throws IOException { + Assertions.assertEquals(1, getPeerCount()); - final StorageContainerManager scm2 = getMockStorageContainerManager( - getConfig("scm2", 9898)); - try { - scm2.getScmHAManager().getRatisServer().start(); - final AddSCMRequest request = new AddSCMRequest( - clusterID, scm2.getScmId(), - "localhost:" + scm2.getScmHAManager().getRatisServer() - .getDivision().getRaftServer().getServerRpc() - .getInetSocketAddress().getPort()); - primarySCMHAManager.addSCM(request); - Assertions.assertEquals(2, primarySCMHAManager.getRatisServer() - .getDivision().getGroup().getPeers().size()); - } finally { - scm2.getScmHAManager().getRatisServer().stop(); - } + follower.start(); + final AddSCMRequest request = new AddSCMRequest( + clusterID, FOLLOWER_SCM_ID, getFollowerAddress()); + primarySCMHAManager.addSCM(request); + Assertions.assertEquals(2, getPeerCount()); } @Test - public void testHARingRemovalErrors() throws IOException, + @Order(2) // requires testAddSCM + void testRemoveSCM() throws IOException { + assumeThat(getPeerCount()).isEqualTo(2); + + final RemoveSCMRequest removeSCMRequest = new RemoveSCMRequest( + clusterID, FOLLOWER_SCM_ID, getFollowerAddress()); + primarySCMHAManager.removeSCM(removeSCMRequest); + assertEquals(1, getPeerCount()); + } + + private int getPeerCount() { + return primarySCMHAManager.getRatisServer() + .getDivision().getGroup().getPeers().size(); + } + + private String getRaftServerAddress(SCMRatisServer ratisServer) { + return "localhost:" + ratisServer.getDivision() + .getRaftServer() + .getServerRpc() + .getInetSocketAddress() + .getPort(); + } + + private String getFollowerAddress() { + return getRaftServerAddress(follower); + } + + @Test + void testHARingRemovalErrors() throws IOException, AuthenticationException { OzoneConfiguration config = new OzoneConfiguration(); config.set(ScmConfigKeys.OZONE_SCM_PRIMORDIAL_NODE_ID_KEY, "scm1"); @@ -160,39 +198,9 @@ public void testHARingRemovalErrors() throws IOException, scm2.getScmHAManager().getRatisServer().stop(); } } - @Test - public void testRemoveSCM() throws IOException, InterruptedException { - Assertions.assertEquals(1, primarySCMHAManager.getRatisServer() - .getDivision().getGroup().getPeers().size()); - - final StorageContainerManager scm2 = getMockStorageContainerManager( - getConfig("scm2", 9898)); - try { - scm2.getScmHAManager().getRatisServer().start(); - final AddSCMRequest addSCMRequest = new AddSCMRequest( - clusterID, scm2.getScmId(), - "localhost:" + scm2.getScmHAManager().getRatisServer() - .getDivision().getRaftServer().getServerRpc() - .getInetSocketAddress().getPort()); - primarySCMHAManager.addSCM(addSCMRequest); - Assertions.assertEquals(2, primarySCMHAManager.getRatisServer() - .getDivision().getGroup().getPeers().size()); - - final RemoveSCMRequest removeSCMRequest = new RemoveSCMRequest( - clusterID, scm2.getScmId(), "localhost:" + - scm2.getScmHAManager().getRatisServer().getDivision() - .getRaftServer().getServerRpc().getInetSocketAddress().getPort()); - primarySCMHAManager.removeSCM(removeSCMRequest); - Assertions.assertEquals(1, primarySCMHAManager.getRatisServer() - .getDivision().getGroup().getPeers().size()); - } finally { - scm2.getScmHAManager().getRatisServer().stop(); - } - } - private StorageContainerManager getMockStorageContainerManager( - OzoneConfiguration conf) throws IOException { - final String scmID = UUID.randomUUID().toString(); + private StorageContainerManager getMockStorageContainerManager(String scmID, int port) throws IOException { + OzoneConfiguration conf = getConfig(scmID, port); final DBStore dbStore = mock(DBStore.class); final SCMContext scmContext = mock(SCMContext.class); @@ -217,6 +225,7 @@ private StorageContainerManager getMockStorageContainerManager( mock(SCMDatanodeProtocolServer.class); when(scm.getClusterId()).thenReturn(clusterID); + when(scm.getConfiguration()).thenReturn(conf); when(scm.getScmId()).thenReturn(scmID); when(scm.getScmMetadataStore()).thenReturn(metadataStore); when(scm.getScmNodeDetails()).thenReturn(nodeDetails); @@ -235,12 +244,33 @@ private StorageContainerManager getMockStorageContainerManager( when(scmHANodeDetails.getLocalNodeDetails()).thenReturn(nodeDetails); when(blockManager.getDeletedBlockLog()).thenReturn(deletedBlockLog); when(dbStore.initBatchOperation()).thenReturn(batchOperation); - when(nodeDetails.getRatisHostPortStr()).thenReturn("localhost:" + - conf.get(ScmConfigKeys.OZONE_SCM_RATIS_PORT_KEY)); + when(nodeDetails.getRatisHostPortStr()).thenReturn("localhost:" + port); when(scm.getSystemClock()).thenReturn(Clock.system(ZoneOffset.UTC)); + if (FOLLOWER_SCM_ID.equals(scmID)) { + final SCMNodeDetails leaderNodeDetails = mock(SCMNodeDetails.class); + final List peerNodeDetails = singletonList(leaderNodeDetails); + when(scmHANodeDetails.getPeerNodeDetails()).thenReturn(peerNodeDetails); + when(leaderNodeDetails.getNodeId()).thenReturn(LEADER_SCM_ID); + when(leaderNodeDetails.getGrpcPort()).thenReturn(LEADER_PORT); + when(leaderNodeDetails.getRatisHostPortStr()).thenReturn("localhost:" + LEADER_PORT); + InetSocketAddress rpcAddress = NetUtils.createSocketAddr("localhost", LEADER_PORT); + when(leaderNodeDetails.getRpcAddress()).thenReturn(rpcAddress); + when(leaderNodeDetails.getInetAddress()).thenReturn(rpcAddress.getAddress()); + } + + DBCheckpoint checkpoint = mock(DBCheckpoint.class); + SCMSnapshotProvider scmSnapshotProvider = mock(SCMSnapshotProvider.class); + when(scmSnapshotProvider.getSCMDBSnapshot(LEADER_SCM_ID)) + .thenReturn(checkpoint); + final SCMHAManager manager = new SCMHAManagerImpl(conf, - new SecurityConfig(conf), scm); + new SecurityConfig(conf), scm) { + @Override + protected SCMSnapshotProvider newScmSnapshotProvider(StorageContainerManager storageContainerManager) { + return scmSnapshotProvider; + } + }; when(scm.getScmHAManager()).thenReturn(manager); return scm; } diff --git a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/node/TestDeadNodeHandler.java b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/node/TestDeadNodeHandler.java index 168fdd11a57b..0f65cdb10870 100644 --- a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/node/TestDeadNodeHandler.java +++ b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/node/TestDeadNodeHandler.java @@ -47,6 +47,7 @@ .StorageContainerDatanodeProtocolProtos.StorageReportProto; import org.apache.hadoop.hdds.scm.ScmConfigKeys; import org.apache.hadoop.hdds.scm.HddsTestUtils; +import org.apache.hadoop.hdds.scm.block.DeletedBlockLog; import org.apache.hadoop.hdds.scm.container.ContainerID; import org.apache.hadoop.hdds.scm.container.ContainerManager; import org.apache.hadoop.hdds.scm.container.ContainerNotFoundException; @@ -90,6 +91,7 @@ public class TestDeadNodeHandler { private EventQueue eventQueue; private String storageDir; private SCMContext scmContext; + private DeletedBlockLog deletedBlockLog; @BeforeEach public void setup() throws IOException, AuthenticationException { @@ -117,8 +119,9 @@ public void setup() throws IOException, AuthenticationException { pipelineManager.setPipelineProvider(RATIS, mockRatisProvider); containerManager = scm.getContainerManager(); + deletedBlockLog = Mockito.mock(DeletedBlockLog.class); deadNodeHandler = new DeadNodeHandler(nodeManager, - Mockito.mock(PipelineManager.class), containerManager); + Mockito.mock(PipelineManager.class), containerManager, deletedBlockLog); healthyReadOnlyNodeHandler = new HealthyReadOnlyNodeHandler(nodeManager, pipelineManager); @@ -134,6 +137,7 @@ public void teardown() { } @Test + @SuppressWarnings("checkstyle:MethodLength") public void testOnMessage() throws Exception { //GIVEN DatanodeDetails datanode1 = MockDatanodeDetails.randomDatanodeDetails(); @@ -233,6 +237,9 @@ public void testOnMessage() throws Exception { Assertions.assertFalse( nodeManager.getClusterNetworkTopologyMap().contains(datanode1)); + Mockito.verify(deletedBlockLog, Mockito.times(0)) + .onDatanodeDead(datanode1.getUuid()); + Set container1Replicas = containerManager .getContainerReplicas(ContainerID.valueOf(container1.getContainerID())); Assertions.assertEquals(2, container1Replicas.size()); @@ -260,6 +267,9 @@ public void testOnMessage() throws Exception { Assertions.assertEquals(0, nodeManager.getCommandQueueCount(datanode1.getUuid(), cmd.getType())); + Mockito.verify(deletedBlockLog, Mockito.times(1)) + .onDatanodeDead(datanode1.getUuid()); + container1Replicas = containerManager .getContainerReplicas(ContainerID.valueOf(container1.getContainerID())); Assertions.assertEquals(1, container1Replicas.size()); diff --git a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/node/TestSCMNodeManager.java b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/node/TestSCMNodeManager.java index e3da551c3edc..a3decb0efb54 100644 --- a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/node/TestSCMNodeManager.java +++ b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/node/TestSCMNodeManager.java @@ -954,16 +954,17 @@ scmStorageConfig, eventPublisher, new NetworkTopologyImpl(conf), @Test public void testProcessCommandQueueReport() - throws IOException, NodeNotFoundException { + throws IOException, NodeNotFoundException, AuthenticationException { OzoneConfiguration conf = new OzoneConfiguration(); SCMStorageConfig scmStorageConfig = mock(SCMStorageConfig.class); when(scmStorageConfig.getClusterID()).thenReturn("xyz111"); EventPublisher eventPublisher = mock(EventPublisher.class); HDDSLayoutVersionManager lvm = new HDDSLayoutVersionManager(scmStorageConfig.getLayoutVersion()); + createNodeManager(getConf()); SCMNodeManager nodeManager = new SCMNodeManager(conf, scmStorageConfig, eventPublisher, new NetworkTopologyImpl(conf), - SCMContext.emptyContext(), lvm); + scmContext, lvm); LayoutVersionProto layoutInfo = toLayoutVersionProto( lvm.getMetadataLayoutVersion(), lvm.getSoftwareLayoutVersion()); diff --git a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/pipeline/TestWritableRatisContainerProvider.java b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/pipeline/TestWritableRatisContainerProvider.java new file mode 100644 index 000000000000..d5d9208adae4 --- /dev/null +++ b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/pipeline/TestWritableRatisContainerProvider.java @@ -0,0 +1,179 @@ +/* + * 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.hdds.scm.pipeline; + +import org.apache.hadoop.hdds.client.RatisReplicationConfig; +import org.apache.hadoop.hdds.client.ReplicationConfig; +import org.apache.hadoop.hdds.conf.OzoneConfiguration; +import org.apache.hadoop.hdds.protocol.proto.HddsProtos; +import org.apache.hadoop.hdds.scm.PipelineChoosePolicy; +import org.apache.hadoop.hdds.scm.container.ContainerInfo; +import org.apache.hadoop.hdds.scm.container.ContainerManager; +import org.apache.hadoop.hdds.scm.container.common.helpers.ExcludeList; +import org.apache.hadoop.hdds.scm.exceptions.SCMException; +import org.apache.hadoop.hdds.scm.pipeline.choose.algorithms.RandomPipelineChoosePolicy; +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptySet; +import static java.util.Collections.singletonList; +import static org.apache.hadoop.hdds.scm.pipeline.Pipeline.PipelineState.OPEN; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class TestWritableRatisContainerProvider { + + private static final ReplicationConfig REPLICATION_CONFIG = + RatisReplicationConfig.getInstance(HddsProtos.ReplicationFactor.THREE); + private static final String OWNER = "owner"; + private static final int CONTAINER_SIZE = 1234; + private static final ExcludeList NO_EXCLUSION = new ExcludeList(); + + private final OzoneConfiguration conf = new OzoneConfiguration(); + private final PipelineChoosePolicy policy = new RandomPipelineChoosePolicy(); + private final AtomicLong containerID = new AtomicLong(1); + + @Mock + private PipelineManager pipelineManager; + + @Mock + private ContainerManager containerManager; + + @Test + void returnsExistingContainer() throws Exception { + Pipeline pipeline = MockPipeline.createPipeline(3); + ContainerInfo existingContainer = pipelineHasContainer(pipeline); + + existingPipelines(pipeline); + + ContainerInfo container = createSubject().getContainer(CONTAINER_SIZE, REPLICATION_CONFIG, OWNER, NO_EXCLUSION); + + assertSame(existingContainer, container); + verifyPipelineNotCreated(); + } + + @RepeatedTest(100) + void skipsPipelineWithoutContainer() throws Exception { + Pipeline pipeline = MockPipeline.createPipeline(3); + ContainerInfo existingContainer = pipelineHasContainer(pipeline); + + Pipeline pipelineWithoutContainer = MockPipeline.createPipeline(3); + existingPipelines(pipelineWithoutContainer, pipeline); + + ContainerInfo container = createSubject().getContainer(CONTAINER_SIZE, REPLICATION_CONFIG, OWNER, NO_EXCLUSION); + + assertSame(existingContainer, container); + verifyPipelineNotCreated(); + } + + @Test + void createsNewContainerIfNoneFound() throws Exception { + ContainerInfo newContainer = createNewContainerOnDemand(); + + ContainerInfo container = createSubject().getContainer(CONTAINER_SIZE, REPLICATION_CONFIG, OWNER, NO_EXCLUSION); + + assertSame(newContainer, container); + verifyPipelineCreated(); + } + + @Test + void failsIfContainerCannotBeCreated() throws Exception { + throwWhenCreatePipeline(); + + assertThrows(IOException.class, + () -> createSubject().getContainer(CONTAINER_SIZE, REPLICATION_CONFIG, OWNER, NO_EXCLUSION)); + + verifyPipelineCreated(); + } + + private void existingPipelines(Pipeline... pipelines) { + existingPipelines(new ArrayList<>(asList(pipelines))); + } + + private void existingPipelines(List pipelines) { + when(pipelineManager.getPipelines(REPLICATION_CONFIG, OPEN, emptySet(), emptySet())) + .thenReturn(pipelines); + } + + private ContainerInfo pipelineHasContainer(Pipeline pipeline) { + ContainerInfo container = new ContainerInfo.Builder() + .setContainerID(containerID.getAndIncrement()) + .setPipelineID(pipeline.getId()) + .build(); + + when(containerManager.getMatchingContainer(CONTAINER_SIZE, OWNER, pipeline, emptySet())) + .thenReturn(container); + + return container; + } + + private ContainerInfo createNewContainerOnDemand() throws IOException { + Pipeline newPipeline = MockPipeline.createPipeline(3); + when(pipelineManager.createPipeline(REPLICATION_CONFIG)) + .thenReturn(newPipeline); + + when(pipelineManager.getPipelines(REPLICATION_CONFIG, OPEN, emptySet(), emptySet())) + .thenReturn(emptyList()) + .thenReturn(new ArrayList<>(singletonList(newPipeline))); + + return pipelineHasContainer(newPipeline); + } + + private void throwWhenCreatePipeline() throws IOException { + when(pipelineManager.createPipeline(REPLICATION_CONFIG)) + .thenThrow(new SCMException(SCMException.ResultCodes.FAILED_TO_FIND_SUITABLE_NODE)); + } + + private WritableRatisContainerProvider createSubject() { + return new WritableRatisContainerProvider(conf, + pipelineManager, containerManager, policy); + } + + private void verifyPipelineCreated() throws IOException { + verify(pipelineManager, times(2)) + .getPipelines(REPLICATION_CONFIG, OPEN, emptySet(), emptySet()); + verify(pipelineManager) + .createPipeline(REPLICATION_CONFIG); + } + + private void verifyPipelineNotCreated() throws IOException { + verify(pipelineManager, times(1)) + .getPipelines(REPLICATION_CONFIG, OPEN, emptySet(), emptySet()); + verify(pipelineManager, never()) + .createPipeline(REPLICATION_CONFIG); + } + +} diff --git a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/ozone/container/placement/TestDatanodeMetrics.java b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/ozone/container/placement/TestDatanodeMetrics.java index 6ba2fc440a4f..9c9bfad582f7 100644 --- a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/ozone/container/placement/TestDatanodeMetrics.java +++ b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/ozone/container/placement/TestDatanodeMetrics.java @@ -31,13 +31,13 @@ public class TestDatanodeMetrics { @Test public void testSCMNodeMetric() { - SCMNodeStat stat = new SCMNodeStat(100L, 10L, 90L); + SCMNodeStat stat = new SCMNodeStat(100L, 10L, 90L, 0, 80); assertEquals((long) stat.getCapacity().get(), 100L); assertEquals(10L, (long) stat.getScmUsed().get()); assertEquals(90L, (long) stat.getRemaining().get()); SCMNodeMetric metric = new SCMNodeMetric(stat); - SCMNodeStat newStat = new SCMNodeStat(100L, 10L, 90L); + SCMNodeStat newStat = new SCMNodeStat(100L, 10L, 90L, 0, 80); assertEquals(100L, (long) stat.getCapacity().get()); assertEquals(10L, (long) stat.getScmUsed().get()); assertEquals(90L, (long) stat.getRemaining().get()); @@ -53,8 +53,8 @@ public void testSCMNodeMetric() { assertTrue(metric.isGreater(zeroMetric.get())); // Another case when nodes have similar weight - SCMNodeStat stat1 = new SCMNodeStat(10000000L, 50L, 9999950L); - SCMNodeStat stat2 = new SCMNodeStat(10000000L, 51L, 9999949L); + SCMNodeStat stat1 = new SCMNodeStat(10000000L, 50L, 9999950L, 0, 100000); + SCMNodeStat stat2 = new SCMNodeStat(10000000L, 51L, 9999949L, 0, 100000); assertTrue(new SCMNodeMetric(stat2).isGreater(stat1)); } } diff --git a/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/UsageInfoSubcommand.java b/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/UsageInfoSubcommand.java index d46513b24bbd..b967fa0658c0 100644 --- a/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/UsageInfoSubcommand.java +++ b/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/UsageInfoSubcommand.java @@ -155,8 +155,16 @@ private void printInfo(DatanodeUsage info) { + " B", StringUtils.byteDesc(info.getRemaining())); System.out.printf("%-13s: %s %n", "Remaining %", PERCENT_FORMAT.format(info.getRemainingRatio())); - System.out.printf("%-13s: %d %n%n", "Container(s)", + System.out.printf("%-13s: %d %n", "Container(s)", info.getContainerCount()); + System.out.printf("%-24s: %s (%s) %n", "Container Pre-allocated", + info.getCommitted() + " B", StringUtils.byteDesc(info.getCommitted())); + System.out.printf("%-24s: %s (%s) %n", "Remaining Allocatable", + (info.getRemaining() - info.getCommitted()) + " B", + StringUtils.byteDesc((info.getRemaining() - info.getCommitted()))); + System.out.printf("%-24s: %s (%s) %n%n", "Free Space To Spare", + info.getFreeSpaceToSpare() + " B", + StringUtils.byteDesc(info.getFreeSpaceToSpare())); } /** @@ -181,6 +189,8 @@ private static class DatanodeUsage { private long capacity = 0; private long used = 0; private long remaining = 0; + private long committed = 0; + private long freeSpaceToSpare = 0; private long containerCount = 0; DatanodeUsage(HddsProtos.DatanodeUsageInfoProto proto) { @@ -196,9 +206,15 @@ private static class DatanodeUsage { if (proto.hasRemaining()) { remaining = proto.getRemaining(); } + if (proto.hasCommitted()) { + committed = proto.getCommitted(); + } if (proto.hasContainerCount()) { containerCount = proto.getContainerCount(); } + if (proto.hasFreeSpaceToSpare()) { + freeSpaceToSpare = proto.getFreeSpaceToSpare(); + } } public DatanodeDetails getDatanodeDetails() { @@ -220,6 +236,12 @@ public long getOzoneUsed() { public long getRemaining() { return remaining; } + public long getCommitted() { + return committed; + } + public long getFreeSpaceToSpare() { + return freeSpaceToSpare; + } public long getContainerCount() { return containerCount; diff --git a/hadoop-hdds/tools/src/test/java/org/apache/hadoop/hdds/scm/cli/datanode/TestUsageInfoSubcommand.java b/hadoop-hdds/tools/src/test/java/org/apache/hadoop/hdds/scm/cli/datanode/TestUsageInfoSubcommand.java index 0cc8ed9be639..a52a0a7ed8f5 100644 --- a/hadoop-hdds/tools/src/test/java/org/apache/hadoop/hdds/scm/cli/datanode/TestUsageInfoSubcommand.java +++ b/hadoop-hdds/tools/src/test/java/org/apache/hadoop/hdds/scm/cli/datanode/TestUsageInfoSubcommand.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.codec.CharEncoding; import org.apache.hadoop.hdds.protocol.MockDatanodeDetails; import org.apache.hadoop.hdds.protocol.proto.HddsProtos; import org.apache.hadoop.hdds.scm.client.ScmClient; @@ -97,6 +98,38 @@ public void testCorrectJsonValuesInReport() throws IOException { json.get(0).get("containerCount").longValue()); } + @Test + public void testOutputDataFieldsAligning() throws IOException { + // given + ScmClient scmClient = mock(ScmClient.class); + Mockito.when(scmClient.getDatanodeUsageInfo( + Mockito.anyBoolean(), Mockito.anyInt())) + .thenAnswer(invocation -> getUsageProto()); + + CommandLine c = new CommandLine(cmd); + c.parseArgs("-m"); + + // when + cmd.execute(scmClient); + + // then + String output = outContent.toString(CharEncoding.UTF_8); + Assertions.assertTrue(output.contains("UUID :")); + Assertions.assertTrue(output.contains("IP Address :")); + Assertions.assertTrue(output.contains("Hostname :")); + Assertions.assertTrue(output.contains("Capacity :")); + Assertions.assertTrue(output.contains("Total Used :")); + Assertions.assertTrue(output.contains("Total Used % :")); + Assertions.assertTrue(output.contains("Ozone Used :")); + Assertions.assertTrue(output.contains("Ozone Used % :")); + Assertions.assertTrue(output.contains("Remaining :")); + Assertions.assertTrue(output.contains("Remaining % :")); + Assertions.assertTrue(output.contains("Container(s) :")); + Assertions.assertTrue(output.contains("Container Pre-allocated :")); + Assertions.assertTrue(output.contains("Remaining Allocatable :")); + Assertions.assertTrue(output.contains("Free Space To Spare :")); + } + private List getUsageProto() { List result = new ArrayList<>(); result.add(HddsProtos.DatanodeUsageInfoProto.newBuilder() diff --git a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/checksum/ECFileChecksumHelper.java b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/checksum/ECFileChecksumHelper.java index 3da23688418f..13ba57169878 100644 --- a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/checksum/ECFileChecksumHelper.java +++ b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/checksum/ECFileChecksumHelper.java @@ -186,13 +186,10 @@ private List getChunkInfos(OmKeyLocationInfo LOG.debug("Initializing BlockInputStream for get key to access {}", blockID.getContainerID()); } - xceiverClientSpi = - getXceiverClientFactory().acquireClientForReadData(pipeline); + xceiverClientSpi = getXceiverClientFactory().acquireClientForReadData(pipeline); - ContainerProtos.DatanodeBlockID datanodeBlockID = blockID - .getDatanodeBlockIDProtobuf(); ContainerProtos.GetBlockResponseProto response = ContainerProtocolCalls - .getBlock(xceiverClientSpi, datanodeBlockID, token); + .getBlock(xceiverClientSpi, blockID, token, pipeline.getReplicaIndexes()); chunks = response.getBlockData().getChunksList(); } finally { diff --git a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/checksum/ReplicatedFileChecksumHelper.java b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/checksum/ReplicatedFileChecksumHelper.java index 09f9c7d037e9..016121ce1a9b 100644 --- a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/checksum/ReplicatedFileChecksumHelper.java +++ b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/checksum/ReplicatedFileChecksumHelper.java @@ -149,13 +149,9 @@ protected List getChunkInfos( LOG.debug("Initializing BlockInputStream for get key to access {}", blockID.getContainerID()); } - xceiverClientSpi = - getXceiverClientFactory().acquireClientForReadData(pipeline); - - ContainerProtos.DatanodeBlockID datanodeBlockID = blockID - .getDatanodeBlockIDProtobuf(); + xceiverClientSpi = getXceiverClientFactory().acquireClientForReadData(pipeline); ContainerProtos.GetBlockResponseProto response = ContainerProtocolCalls - .getBlock(xceiverClientSpi, datanodeBlockID, token); + .getBlock(xceiverClientSpi, blockID, token, pipeline.getReplicaIndexes()); chunks = response.getBlockData().getChunksList(); } finally { diff --git a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/io/ECBlockOutputStreamEntry.java b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/io/ECBlockOutputStreamEntry.java index 241754a57f19..2cf2ab0cf9c2 100644 --- a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/io/ECBlockOutputStreamEntry.java +++ b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/io/ECBlockOutputStreamEntry.java @@ -37,7 +37,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -248,8 +247,7 @@ Collection getFailedServers() { @VisibleForTesting Pipeline createSingleECBlockPipeline(Pipeline ecPipeline, DatanodeDetails node, int replicaIndex) { - Map indiciesForSinglePipeline = new HashMap<>(); - indiciesForSinglePipeline.put(node, replicaIndex); + Map indiciesForSinglePipeline = Collections.singletonMap(node, replicaIndex); return Pipeline.newBuilder() .setId(ecPipeline.getId()) .setReplicationConfig(ecPipeline.getReplicationConfig()) diff --git a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/io/KeyInputStream.java b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/io/KeyInputStream.java index 4843c1c45e6c..76fa1e394f6c 100644 --- a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/io/KeyInputStream.java +++ b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/io/KeyInputStream.java @@ -25,6 +25,7 @@ import java.util.stream.Collectors; import org.apache.hadoop.hdds.client.BlockID; +import org.apache.hadoop.hdds.scm.OzoneClientConfig; import org.apache.hadoop.hdds.scm.XceiverClientFactory; import org.apache.hadoop.hdds.scm.storage.BlockExtendedInputStream; import org.apache.hadoop.hdds.scm.storage.BlockLocationInfo; @@ -58,9 +59,9 @@ private static List createStreams( OmKeyInfo keyInfo, List blockInfos, XceiverClientFactory xceiverClientFactory, - boolean verifyChecksum, Function retryFunction, - BlockInputStreamFactory blockStreamFactory) { + BlockInputStreamFactory blockStreamFactory, + OzoneClientConfig config) throws IOException { List partStreams = new ArrayList<>(); for (OmKeyLocationInfo omKeyLocationInfo : blockInfos) { if (LOG.isDebugEnabled()) { @@ -91,9 +92,9 @@ private static List createStreams( omKeyLocationInfo, omKeyLocationInfo.getPipeline(), omKeyLocationInfo.getToken(), - verifyChecksum, xceiverClientFactory, - retry); + retry, + config); partStreams.add(stream); } return partStreams; @@ -117,13 +118,13 @@ private static BlockLocationInfo getBlockLocationInfo(OmKeyInfo newKeyInfo, private static LengthInputStream getFromOmKeyInfo( OmKeyInfo keyInfo, XceiverClientFactory xceiverClientFactory, - boolean verifyChecksum, Function retryFunction, BlockInputStreamFactory blockStreamFactory, - List locationInfos) { + List locationInfos, + OzoneClientConfig config) throws IOException { List streams = createStreams(keyInfo, - locationInfos, xceiverClientFactory, verifyChecksum, retryFunction, - blockStreamFactory); + locationInfos, xceiverClientFactory, retryFunction, + blockStreamFactory, config); KeyInputStream keyInputStream = new KeyInputStream(keyInfo.getKeyName(), streams); return new LengthInputStream(keyInputStream, keyInputStream.getLength()); @@ -134,20 +135,22 @@ private static LengthInputStream getFromOmKeyInfo( */ public static LengthInputStream getFromOmKeyInfo(OmKeyInfo keyInfo, XceiverClientFactory xceiverClientFactory, - boolean verifyChecksum, Function retryFunction, - BlockInputStreamFactory blockStreamFactory) { + Function retryFunction, + BlockInputStreamFactory blockStreamFactory, + OzoneClientConfig config) throws IOException { List keyLocationInfos = keyInfo .getLatestVersionLocations().getBlocksLatestVersionOnly(); - return getFromOmKeyInfo(keyInfo, xceiverClientFactory, verifyChecksum, - retryFunction, blockStreamFactory, keyLocationInfos); + return getFromOmKeyInfo(keyInfo, xceiverClientFactory, + retryFunction, blockStreamFactory, keyLocationInfos, config); } public static List getStreamsFromKeyInfo(OmKeyInfo keyInfo, - XceiverClientFactory xceiverClientFactory, boolean verifyChecksum, + XceiverClientFactory xceiverClientFactory, Function retryFunction, - BlockInputStreamFactory blockStreamFactory) { + BlockInputStreamFactory blockStreamFactory, + OzoneClientConfig config) throws IOException { List keyLocationInfos = keyInfo .getLatestVersionLocations().getBlocksLatestVersionOnly(); @@ -162,7 +165,8 @@ public static List getStreamsFromKeyInfo(OmKeyInfo keyInfo, // Create a KeyInputStream for each part. for (List locationInfo : partsToBlocksMap.values()) { lengthInputStreams.add(getFromOmKeyInfo(keyInfo, xceiverClientFactory, - verifyChecksum, retryFunction, blockStreamFactory, locationInfo)); + retryFunction, blockStreamFactory, locationInfo, + config)); } return lengthInputStreams; } diff --git a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java index 8d61f8ef8609..222823c454a7 100644 --- a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java +++ b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java @@ -50,6 +50,7 @@ import org.apache.hadoop.hdds.scm.XceiverClientFactory; import org.apache.hadoop.hdds.scm.XceiverClientManager; import org.apache.hadoop.hdds.scm.client.ClientTrustManager; +import org.apache.hadoop.hdds.scm.storage.ByteBufferStreamOutput; import org.apache.hadoop.hdds.security.x509.certificate.client.CACertificateProvider; import org.apache.hadoop.hdds.scm.client.HddsClientUtils; import org.apache.hadoop.hdds.scm.pipeline.Pipeline; @@ -1834,11 +1835,16 @@ public OzoneOutputStream createMultipartKey( long size, int partNumber, String uploadID) throws IOException { final OpenKeySession openKey = newMultipartOpenKey( volumeName, bucketName, keyName, size, partNumber, uploadID, false); + return createMultipartOutputStream(openKey, uploadID, partNumber); + } + + private OzoneOutputStream createMultipartOutputStream( + OpenKeySession openKey, String uploadID, int partNumber + ) throws IOException { KeyOutputStream keyOutputStream = createKeyOutputStream(openKey) .setMultipartNumber(partNumber) .setMultipartUploadID(uploadID) .setIsMultipartKey(true) - .setAtomicKeyCreation(isS3GRequest.get()) .build(); return createOutputStream(openKey, keyOutputStream); } @@ -1854,29 +1860,25 @@ public OzoneDataStreamOutput createMultipartStreamKey( throws IOException { final OpenKeySession openKey = newMultipartOpenKey( volumeName, bucketName, keyName, size, partNumber, uploadID, true); - // Amazon S3 never adds partial objects, So for S3 requests we need to - // set atomicKeyCreation to true - // refer: https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObject.html - KeyDataStreamOutput keyOutputStream = - new KeyDataStreamOutput.Builder() - .setHandler(openKey) - .setXceiverClientManager(xceiverClientManager) - .setOmClient(ozoneManagerClient) - .setReplicationConfig(openKey.getKeyInfo().getReplicationConfig()) - .setMultipartNumber(partNumber) - .setMultipartUploadID(uploadID) - .setIsMultipartKey(true) - .enableUnsafeByteBufferConversion(unsafeByteBufferConversion) - .setConfig(clientConfig) - .setAtomicKeyCreation(isS3GRequest.get()) - .build(); - keyOutputStream - .addPreallocateBlocks( - openKey.getKeyInfo().getLatestVersionLocations(), - openKey.getOpenVersion()); - final OzoneOutputStream out = createSecureOutputStream( - openKey, keyOutputStream, null); - return new OzoneDataStreamOutput(out != null ? out : keyOutputStream); + final ByteBufferStreamOutput out; + ReplicationConfig replicationConfig = openKey.getKeyInfo().getReplicationConfig(); + if (replicationConfig.getReplicationType() == HddsProtos.ReplicationType.RATIS) { + KeyDataStreamOutput keyOutputStream = newKeyOutputStreamBuilder() + .setHandler(openKey) + .setReplicationConfig(replicationConfig) + .setMultipartNumber(partNumber) + .setMultipartUploadID(uploadID) + .setIsMultipartKey(true) + .build(); + keyOutputStream.addPreallocateBlocks( + openKey.getKeyInfo().getLatestVersionLocations(), + openKey.getOpenVersion()); + final OzoneOutputStream secureOut = createSecureOutputStream(openKey, keyOutputStream, null); + out = secureOut != null ? secureOut : keyOutputStream; + } else { + out = createMultipartOutputStream(openKey, uploadID, partNumber); + } + return new OzoneDataStreamOutput(out); } @Override @@ -2221,9 +2223,8 @@ private OzoneInputStream createInputStream( if (feInfo == null) { LengthInputStream lengthInputStream = KeyInputStream - .getFromOmKeyInfo(keyInfo, xceiverClientManager, - clientConfig.isChecksumVerify(), retryFunction, - blockInputStreamFactory); + .getFromOmKeyInfo(keyInfo, xceiverClientManager, retryFunction, + blockInputStreamFactory, clientConfig); try { final GDPRSymmetricKey gk = getGDPRSymmetricKey( keyInfo.getMetadata(), Cipher.DECRYPT_MODE); @@ -2238,9 +2239,8 @@ private OzoneInputStream createInputStream( } else if (!keyInfo.getLatestVersionLocations().isMultipartKey()) { // Regular Key with FileEncryptionInfo LengthInputStream lengthInputStream = KeyInputStream - .getFromOmKeyInfo(keyInfo, xceiverClientManager, - clientConfig.isChecksumVerify(), retryFunction, - blockInputStreamFactory); + .getFromOmKeyInfo(keyInfo, xceiverClientManager, retryFunction, + blockInputStreamFactory, clientConfig); final KeyProvider.KeyVersion decrypted = getDEK(feInfo); final CryptoInputStream cryptoIn = new CryptoInputStream(lengthInputStream.getWrappedStream(), @@ -2250,9 +2250,8 @@ private OzoneInputStream createInputStream( } else { // Multipart Key with FileEncryptionInfo List lengthInputStreams = KeyInputStream - .getStreamsFromKeyInfo(keyInfo, xceiverClientManager, - clientConfig.isChecksumVerify(), retryFunction, - blockInputStreamFactory); + .getStreamsFromKeyInfo(keyInfo, xceiverClientManager, retryFunction, + blockInputStreamFactory, clientConfig); final KeyProvider.KeyVersion decrypted = getDEK(feInfo); List cryptoInputStreams = new ArrayList<>(); @@ -2273,25 +2272,33 @@ private OzoneDataStreamOutput createDataStreamOutput(OpenKeySession openKey) throws IOException { final ReplicationConfig replicationConfig = openKey.getKeyInfo().getReplicationConfig(); + final ByteBufferStreamOutput out; + if (replicationConfig.getReplicationType() == HddsProtos.ReplicationType.RATIS) { + KeyDataStreamOutput keyOutputStream = newKeyOutputStreamBuilder() + .setHandler(openKey) + .setReplicationConfig(replicationConfig) + .build(); + keyOutputStream.addPreallocateBlocks( + openKey.getKeyInfo().getLatestVersionLocations(), + openKey.getOpenVersion()); + final OzoneOutputStream secureOut = createSecureOutputStream(openKey, keyOutputStream, null); + out = secureOut != null ? secureOut : keyOutputStream; + } else { + out = createOutputStream(openKey); + } + return new OzoneDataStreamOutput(out); + } + + private KeyDataStreamOutput.Builder newKeyOutputStreamBuilder() { // Amazon S3 never adds partial objects, So for S3 requests we need to // set atomicKeyCreation to true // refer: https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObject.html - KeyDataStreamOutput keyOutputStream = - new KeyDataStreamOutput.Builder() - .setHandler(openKey) - .setXceiverClientManager(xceiverClientManager) - .setOmClient(ozoneManagerClient) - .setReplicationConfig(replicationConfig) - .enableUnsafeByteBufferConversion(unsafeByteBufferConversion) - .setConfig(clientConfig) - .setAtomicKeyCreation(isS3GRequest.get()) - .build(); - keyOutputStream - .addPreallocateBlocks(openKey.getKeyInfo().getLatestVersionLocations(), - openKey.getOpenVersion()); - final OzoneOutputStream out = createSecureOutputStream( - openKey, keyOutputStream, null); - return new OzoneDataStreamOutput(out != null ? out : keyOutputStream); + return new KeyDataStreamOutput.Builder() + .setXceiverClientManager(xceiverClientManager) + .setOmClient(ozoneManagerClient) + .enableUnsafeByteBufferConversion(unsafeByteBufferConversion) + .setConfig(clientConfig) + .setAtomicKeyCreation(isS3GRequest.get()); } private OzoneOutputStream createOutputStream(OpenKeySession openKey) diff --git a/hadoop-ozone/client/src/test/java/org/apache/hadoop/ozone/client/io/TestKeyInputStreamEC.java b/hadoop-ozone/client/src/test/java/org/apache/hadoop/ozone/client/io/TestKeyInputStreamEC.java index 28e9b8ac3c61..df6ef9bc0506 100644 --- a/hadoop-ozone/client/src/test/java/org/apache/hadoop/ozone/client/io/TestKeyInputStreamEC.java +++ b/hadoop-ozone/client/src/test/java/org/apache/hadoop/ozone/client/io/TestKeyInputStreamEC.java @@ -20,8 +20,10 @@ import org.apache.hadoop.hdds.client.BlockID; import org.apache.hadoop.hdds.client.ECReplicationConfig; import org.apache.hadoop.hdds.client.ReplicationConfig; +import org.apache.hadoop.hdds.conf.OzoneConfiguration; import org.apache.hadoop.hdds.protocol.DatanodeDetails; import org.apache.hadoop.hdds.protocol.MockDatanodeDetails; +import org.apache.hadoop.hdds.scm.OzoneClientConfig; import org.apache.hadoop.hdds.scm.pipeline.Pipeline; import org.apache.hadoop.hdds.scm.pipeline.PipelineID; import org.apache.hadoop.hdds.scm.storage.BlockExtendedInputStream; @@ -40,7 +42,6 @@ import static org.apache.hadoop.ozone.OzoneConsts.MB; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -49,6 +50,8 @@ */ public class TestKeyInputStreamEC { + private OzoneConfiguration conf = new OzoneConfiguration(); + @Test public void testReadAgainstLargeBlockGroup() throws IOException { int dataBlocks = 10; @@ -68,10 +71,13 @@ public void testReadAgainstLargeBlockGroup() throws IOException { BlockInputStreamFactory mockStreamFactory = mock(BlockInputStreamFactory.class); when(mockStreamFactory.create(any(), any(), any(), any(), - anyBoolean(), any(), any())).thenReturn(blockInputStream); + any(), any(), any())).thenReturn(blockInputStream); + OzoneClientConfig clientConfig = conf.getObject(OzoneClientConfig.class); + clientConfig.setChecksumVerify(true); try (LengthInputStream kis = KeyInputStream.getFromOmKeyInfo(keyInfo, - null, true, null, mockStreamFactory)) { + null, null, mockStreamFactory, + clientConfig)) { byte[] buf = new byte[100]; int readBytes = kis.read(buf, 0, 100); Assertions.assertEquals(100, readBytes); diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java index babeb3054875..1767cdb6dc3a 100644 --- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java +++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java @@ -721,6 +721,47 @@ public static String normalizeKey(String keyName, return keyName; } + /** + * Normalizes a given path up to the bucket level. + * + * This method takes a path as input and normalises uptil the bucket level. + * It handles empty, removes leading slashes, and splits the path into + * segments. It then extracts the volume and bucket names, forming a + * normalized path with a single slash. Finally, any remaining segments are + * joined as the key name, returning the complete standardized path. + * + * @param path The path string to be normalized. + * @return The normalized path string. + */ + public static String normalizePathUptoBucket(String path) { + if (path == null || path.isEmpty()) { + return OM_KEY_PREFIX; // Handle empty path + } + + // Remove leading slashes + path = path.replaceAll("^/*", ""); + + String[] segments = path.split(OM_KEY_PREFIX, -1); + + String volumeName = segments[0]; + String bucketName = segments.length > 1 ? segments[1] : ""; + + // Combine volume and bucket. + StringBuilder normalizedPath = new StringBuilder(volumeName); + if (!bucketName.isEmpty()) { + normalizedPath.append(OM_KEY_PREFIX).append(bucketName); + } + + // Add remaining segments as the key + if (segments.length > 2) { + normalizedPath.append(OM_KEY_PREFIX).append( + String.join(OM_KEY_PREFIX, + Arrays.copyOfRange(segments, 2, segments.length))); + } + + return normalizedPath.toString(); + } + /** * For a given service ID, return list of configured OM hosts. diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/OMConfigKeys.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/OMConfigKeys.java index 5dd7579eb916..9b844cc74fdf 100644 --- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/OMConfigKeys.java +++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/OMConfigKeys.java @@ -579,13 +579,20 @@ private OMConfigKeys() { = TimeUnit.DAYS.toMillis(7); public static final String OZONE_OM_SNAPSHOT_DIFF_CLEANUP_SERVICE_RUN_INTERVAL - = "ozone.om.snapshot.diff.cleanup.service.run.internal"; + = "ozone.om.snapshot.diff.cleanup.service.run.interval"; + public static final String + OZONE_OM_SNAPSHOT_CACHE_CLEANUP_SERVICE_RUN_INTERVAL + = "ozone.om.snapshot.cache.cleanup.service.run.interval"; public static final long OZONE_OM_SNAPSHOT_DIFF_CLEANUP_SERVICE_RUN_INTERVAL_DEFAULT = TimeUnit.MINUTES.toMillis(1); + public static final long + OZONE_OM_SNAPSHOT_CACHE_CLEANUP_SERVICE_RUN_INTERVAL_DEFAULT + = TimeUnit.MINUTES.toMillis(1); public static final String OZONE_OM_SNAPSHOT_DIFF_CLEANUP_SERVICE_TIMEOUT = "ozone.om.snapshot.diff.cleanup.service.timeout"; + public static final long OZONE_OM_SNAPSHOT_DIFF_CLEANUP_SERVICE_TIMEOUT_DEFAULT = TimeUnit.MINUTES.toMillis(5); diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/snapshot/SnapshotDiffReportOzone.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/snapshot/SnapshotDiffReportOzone.java index 3d14e266daa4..29bf4deb2a08 100644 --- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/snapshot/SnapshotDiffReportOzone.java +++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/snapshot/SnapshotDiffReportOzone.java @@ -101,16 +101,12 @@ public String toString() { .append(" and snapshot: ") .append(getLaterSnapshotName()) .append(LINE_SEPARATOR); - if (!getDiffList().isEmpty()) { - for (DiffReportEntry entry : getDiffList()) { - str.append(entry.toString()).append(LINE_SEPARATOR); - } - if (StringUtils.isNotEmpty(token)) { - str.append("Next token: ") - .append(token); - } - } else { - str.append("No diff or no more diff for the request parameters."); + for (DiffReportEntry entry : getDiffList()) { + str.append(entry.toString()).append(LINE_SEPARATOR); + } + if (StringUtils.isNotEmpty(token)) { + str.append("Next token: ") + .append(token); } return str.toString(); } diff --git a/hadoop-ozone/dist/src/main/compose/ozonesecure/docker-config b/hadoop-ozone/dist/src/main/compose/ozonesecure/docker-config index 53f1a63d97ae..f5c32ff3aa2c 100644 --- a/hadoop-ozone/dist/src/main/compose/ozonesecure/docker-config +++ b/hadoop-ozone/dist/src/main/compose/ozonesecure/docker-config @@ -84,7 +84,8 @@ OZONE-SITE.XML_ozone.scm.dead.node.interval=45s OZONE-SITE.XML_hdds.container.report.interval=60s OZONE-SITE.XML_ozone.scm.close.container.wait.duration=5s -OZONE-SITE.XML_dfs.container.ratis.datastream.enabled=true +# Ratis streaming is disabled to ensure coverage for both cases +OZONE-SITE.XML_dfs.container.ratis.datastream.enabled=false HDFS-SITE.XML_dfs.datanode.kerberos.principal=dn/dn@EXAMPLE.COM HDFS-SITE.XML_dfs.datanode.kerberos.keytab.file=/etc/security/keytabs/dn.keytab @@ -114,7 +115,7 @@ OZONE-SITE.XML_ozone.s3g.http.auth.kerberos.keytab=/etc/security/keytabs/s3g.key OZONE-SITE.XML_ozone.s3g.http.auth.kerberos.principal=HTTP/s3g@EXAMPLE.COM OZONE-SITE.XML_ozone.httpfs.http.auth.kerberos.keytab=/etc/security/keytabs/httpfs.keytab OZONE-SITE.XML_ozone.httpfs.http.auth.kerberos.principal=HTTP/httpfs@EXAMPLE.COM -OZONE-SITE.XML_ozone.recon.http.auth.kerberos.principal=HTTP/recon@EXAMPLE.COM +OZONE-SITE.XML_ozone.recon.http.auth.kerberos.principal=* OZONE-SITE.XML_ozone.recon.http.auth.kerberos.keytab=/etc/security/keytabs/recon.keytab CORE-SITE.XML_hadoop.http.authentication.simple.anonymous.allowed=false diff --git a/hadoop-ozone/dist/src/main/compose/ozonesecure/test.sh b/hadoop-ozone/dist/src/main/compose/ozonesecure/test.sh index 41f4fa9a580f..6d213f9b5fbe 100755 --- a/hadoop-ozone/dist/src/main/compose/ozonesecure/test.sh +++ b/hadoop-ozone/dist/src/main/compose/ozonesecure/test.sh @@ -39,6 +39,15 @@ execute_robot_test scm security execute_robot_test scm -v SCHEME:ofs -v BUCKET_TYPE:bucket -N ozonefs-ofs-bucket ozonefs/ozonefs.robot +## Exclude virtual-host tests. This is tested separately as it requires additional config. +exclude="--exclude virtual-host" +for bucket in encrypted; do + execute_robot_test s3g -v BUCKET:${bucket} -N s3-${bucket} ${exclude} s3 + # some tests are independent of the bucket type, only need to be run once + ## Exclude virtual-host.robot + exclude="--exclude virtual-host --exclude no-bucket-type" +done + #expects 4 pipelines, should be run before #admincli which creates STANDALONE pipeline execute_robot_test scm recon diff --git a/hadoop-ozone/dist/src/main/compose/testlib.sh b/hadoop-ozone/dist/src/main/compose/testlib.sh index c13ca1bb80e0..505cb1ae77c9 100755 --- a/hadoop-ozone/dist/src/main/compose/testlib.sh +++ b/hadoop-ozone/dist/src/main/compose/testlib.sh @@ -304,9 +304,10 @@ get_output_name() { save_container_logs() { local output_name=$(get_output_name) - local c - for c in $(docker-compose ps "$@" | cut -f1 -d' ' | tail -n +3); do - docker logs "${c}" >> "$RESULT_DIR/docker-${output_name}${c}.log" 2>&1 + local id + for i in $(docker-compose ps -a -q "$@"); do + local c=$(docker ps -a --filter "id=${i}" --format "{{ .Names }}") + docker logs "${i}" >> "$RESULT_DIR/docker-${output_name}${c}.log" 2>&1 done } diff --git a/hadoop-ozone/dist/src/main/license/jar-report.txt b/hadoop-ozone/dist/src/main/license/jar-report.txt index b4f83965f4a1..49473afb73d1 100644 --- a/hadoop-ozone/dist/src/main/license/jar-report.txt +++ b/hadoop-ozone/dist/src/main/license/jar-report.txt @@ -163,7 +163,10 @@ share/ozone/lib/jsp-api.jar share/ozone/lib/jsr305.jar share/ozone/lib/jsr311-api.jar share/ozone/lib/kerb-core.jar +share/ozone/lib/kerb-crypto.jar +share/ozone/lib/kerb-util.jar share/ozone/lib/kerby-asn1.jar +share/ozone/lib/kerby-config.jar share/ozone/lib/kerby-pkix.jar share/ozone/lib/kerby-util.jar share/ozone/lib/kotlin-stdlib-common.jar diff --git a/hadoop-ozone/integration-test/dev-support/findbugsExcludeFile.xml b/hadoop-ozone/integration-test/dev-support/findbugsExcludeFile.xml index 098d27980c3a..3b7a676f28bf 100644 --- a/hadoop-ozone/integration-test/dev-support/findbugsExcludeFile.xml +++ b/hadoop-ozone/integration-test/dev-support/findbugsExcludeFile.xml @@ -36,10 +36,6 @@ - - - - @@ -125,10 +121,6 @@ - - - - diff --git a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestOzoneFileSystem.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/AbstractOzoneFileSystemTest.java similarity index 70% rename from hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestOzoneFileSystem.java rename to hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/AbstractOzoneFileSystemTest.java index b5d83704833c..a83c7bf16a2e 100644 --- a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestOzoneFileSystem.java +++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/AbstractOzoneFileSystemTest.java @@ -18,14 +18,17 @@ package org.apache.hadoop.fs.ozone; +import org.apache.commons.lang3.RandomStringUtils; import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.fs.CommonConfigurationKeysPublic; +import org.apache.hadoop.conf.StorageUnit; +import org.apache.hadoop.fs.BlockLocation; import org.apache.hadoop.fs.FSDataInputStream; import org.apache.hadoop.fs.FSDataOutputStream; import org.apache.hadoop.fs.FileAlreadyExistsException; import org.apache.hadoop.fs.FileStatus; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.InvalidPathException; +import org.apache.hadoop.fs.LocatedFileStatus; import org.apache.hadoop.fs.Path; import org.apache.hadoop.fs.PathFilter; import org.apache.hadoop.fs.PathIsNotEmptyDirectoryException; @@ -53,6 +56,8 @@ import org.apache.hadoop.ozone.client.OzoneKeyDetails; import org.apache.hadoop.ozone.client.OzoneVolume; import org.apache.hadoop.ozone.om.OMConfigKeys; +import org.apache.hadoop.ozone.om.OMMetadataManager; +import org.apache.hadoop.ozone.om.OMMetrics; import org.apache.hadoop.ozone.om.OzonePrefixPathImpl; import org.apache.hadoop.ozone.om.TrashPolicyOzone; import org.apache.hadoop.ozone.om.exceptions.OMException; @@ -62,17 +67,14 @@ import org.apache.hadoop.ozone.om.helpers.OzoneFileStatus; import org.apache.hadoop.ozone.om.protocol.OzoneManagerProtocol; import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.util.Time; import org.apache.ozone.test.GenericTestUtils; import org.apache.ozone.test.TestClock; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestRule; -import org.junit.rules.Timeout; -import org.apache.ozone.test.JUnit5AwareTimeout; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.event.Level; @@ -88,38 +90,48 @@ import java.util.Collection; import java.util.Collections; import java.util.Iterator; +import java.util.List; +import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.UUID; import static java.nio.charset.StandardCharsets.UTF_8; +import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.FS_DEFAULT_NAME_KEY; import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.FS_TRASH_CHECKPOINT_INTERVAL_KEY; import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.FS_TRASH_INTERVAL_KEY; import static org.apache.hadoop.fs.CommonPathCapabilities.FS_ACLS; import static org.apache.hadoop.fs.CommonPathCapabilities.FS_CHECKSUMS; import static org.apache.hadoop.fs.FileSystem.TRASH_PREFIX; +import static org.apache.hadoop.fs.StorageStatistics.CommonStatisticNames.OP_CREATE; +import static org.apache.hadoop.fs.StorageStatistics.CommonStatisticNames.OP_GET_FILE_STATUS; +import static org.apache.hadoop.fs.StorageStatistics.CommonStatisticNames.OP_MKDIRS; +import static org.apache.hadoop.fs.StorageStatistics.CommonStatisticNames.OP_OPEN; import static org.apache.hadoop.fs.contract.ContractTestUtils.assertHasPathCapabilities; import static org.apache.hadoop.fs.ozone.Constants.LISTING_PAGE_SIZE; +import static org.apache.hadoop.fs.ozone.Constants.OZONE_DEFAULT_USER; import static org.apache.hadoop.hdds.protocol.proto.HddsProtos.ReplicationFactor.ONE; import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_ACL_ENABLED; import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_FS_ITERATE_BATCH_SIZE; import static org.apache.hadoop.ozone.OzoneConsts.OM_KEY_PREFIX; import static org.apache.hadoop.ozone.OzoneConsts.OZONE_URI_DELIMITER; import static org.apache.hadoop.ozone.om.helpers.BucketLayout.FILE_SYSTEM_OPTIMIZED; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThrows; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.junit.Assume.assumeFalse; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeFalse; /** * Ozone file system tests that are not covered by contract tests. */ -@RunWith(Parameterized.class) -public class TestOzoneFileSystem { +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +abstract class AbstractOzoneFileSystemTest { private static final float TRASH_INTERVAL = 0.05f; // 3 seconds @@ -131,59 +143,35 @@ public class TestOzoneFileSystem { private static final PathFilter EXCLUDE_TRASH = p -> !p.toUri().getPath().startsWith(TRASH_ROOT.toString()); + private String fsRoot; - @Parameterized.Parameters - public static Collection data() { - return Arrays.asList( - new Object[]{true, true}, - new Object[]{true, false}, - new Object[]{false, true}, - new Object[]{false, false}); - } - - public TestOzoneFileSystem(boolean setDefaultFs, boolean enableOMRatis) { - // Checking whether 'defaultFS' and 'omRatis' flags represents next - // parameter index values. This is to ensure that initialize - // TestOzoneFileSystem#init() function will be invoked only at the - // beginning of every new set of Parameterized.Parameters. - if (enabledFileSystemPaths != setDefaultFs || - omRatisEnabled != enableOMRatis || cluster == null) { - enabledFileSystemPaths = setDefaultFs; - omRatisEnabled = enableOMRatis; - try { - teardown(); - init(); - } catch (Exception e) { - LOG.info("Unexpected exception", e); - fail("Unexpected exception:" + e.getMessage()); - } - } + AbstractOzoneFileSystemTest(boolean setDefaultFs, boolean enableOMRatis, BucketLayout layout) { + enabledFileSystemPaths = setDefaultFs; + omRatisEnabled = enableOMRatis; + bucketLayout = layout; } - /** - * Set a timeout for each test. - */ - @Rule - public TestRule timeout = new JUnit5AwareTimeout(Timeout.seconds(600)); - private static final Logger LOG = - LoggerFactory.getLogger(TestOzoneFileSystem.class); - - private static BucketLayout bucketLayout = BucketLayout.LEGACY; - private static boolean enabledFileSystemPaths; - private static boolean omRatisEnabled; - - private static MiniOzoneCluster cluster; - private static OzoneClient client; - private static OzoneManagerProtocol writeClient; - private static FileSystem fs; - private static OzoneFileSystem o3fs; - private static OzoneBucket ozoneBucket; - private static String volumeName; - private static String bucketName; - private static Trash trash; - - private void init() throws Exception { + LoggerFactory.getLogger(AbstractOzoneFileSystemTest.class); + + private final BucketLayout bucketLayout; + private final boolean enabledFileSystemPaths; + private final boolean omRatisEnabled; + + private MiniOzoneCluster cluster; + private OzoneClient client; + private OzoneManagerProtocol writeClient; + private FileSystem fs; + private OzoneFileSystem o3fs; + private OzoneFSStorageStatistics statistics; + private OzoneBucket ozoneBucket; + private String volumeName; + private String bucketName; + private Trash trash; + private OMMetrics omMetrics; + + @BeforeAll + void init() throws Exception { OzoneConfiguration conf = new OzoneConfiguration(); conf.setFloat(OMConfigKeys.OZONE_FS_TRASH_INTERVAL_KEY, TRASH_INTERVAL); conf.setFloat(FS_TRASH_INTERVAL_KEY, TRASH_INTERVAL); @@ -202,6 +190,7 @@ private void init() throws Exception { .setNumDatanodes(5) .build(); cluster.waitForClusterToBeReady(); + omMetrics = cluster.getOzoneManager().getMetrics(); client = cluster.newClient(); writeClient = client.getObjectStore() @@ -211,21 +200,24 @@ private void init() throws Exception { volumeName = ozoneBucket.getVolumeName(); bucketName = ozoneBucket.getName(); - String rootPath = String.format("%s://%s.%s/", - OzoneConsts.OZONE_URI_SCHEME, bucketName, volumeName); + fsRoot = String.format("%s://%s.%s/", + OzoneConsts.OZONE_URI_SCHEME, bucketName, volumeName); // Set the fs.defaultFS and start the filesystem - conf.set(CommonConfigurationKeysPublic.FS_DEFAULT_NAME_KEY, rootPath); + conf.set(FS_DEFAULT_NAME_KEY, fsRoot); // Set the number of keys to be processed during batch operate. conf.setInt(OZONE_FS_ITERATE_BATCH_SIZE, 5); fs = FileSystem.get(conf); trash = new Trash(conf); - o3fs = (OzoneFileSystem) fs; + o3fs = assertInstanceOf(OzoneFileSystem.class, fs); + statistics = (OzoneFSStorageStatistics) o3fs.getOzoneFSOpsCountStatistics(); + assertEquals(OzoneConsts.OZONE_URI_SCHEME, fs.getUri().getScheme()); + assertEquals(OzoneConsts.OZONE_URI_SCHEME, statistics.getScheme()); } - @AfterClass - public static void teardown() { + @AfterAll + void teardown() { IOUtils.closeQuietly(client); if (cluster != null) { cluster.shutdown(); @@ -233,7 +225,7 @@ public static void teardown() { IOUtils.closeQuietly(fs); } - @After + @AfterEach public void cleanup() { try { deleteRootDir(); @@ -243,28 +235,24 @@ public void cleanup() { } } - public static MiniOzoneCluster getCluster() { + public MiniOzoneCluster getCluster() { return cluster; } - public static FileSystem getFs() { + public FileSystem getFs() { return fs; } - public static void setBucketLayout(BucketLayout bLayout) { - bucketLayout = bLayout; - } - - public static String getBucketName() { + public String getBucketName() { return bucketName; } - public static String getVolumeName() { + public String getVolumeName() { return volumeName; } public BucketLayout getBucketLayout() { - return BucketLayout.DEFAULT; + return bucketLayout; } @Test @@ -284,7 +272,7 @@ public void testCreateFileShouldCheckExistenceOfDirWithSameName() Path parent = new Path("/d1/d2/d3/d4/"); Path file1 = new Path(parent, "key1"); try (FSDataOutputStream outputStream = fs.create(file1, false)) { - assertNotNull("Should be able to create file", outputStream); + assertNotNull(outputStream, "Should be able to create file"); } Path dir1 = new Path("/d1/d2/d3/d4/key2"); @@ -297,7 +285,7 @@ public void testCreateFileShouldCheckExistenceOfDirWithSameName() Path file2 = new Path("/d1/d2/d3/d4/key3"); try (FSDataOutputStream outputStream2 = fs.create(file2, false)) { - assertNotNull("Should be able to create file", outputStream2); + assertNotNull(outputStream2, "Should be able to create file"); } try { fs.mkdirs(file2); @@ -316,10 +304,8 @@ public void testCreateFileShouldCheckExistenceOfDirWithSameName() // Directory FileStatus fileStatus = fs.getFileStatus(parent); - assertEquals("FileStatus did not return the directory", - "/d1/d2/d3/d4", fileStatus.getPath().toUri().getPath()); - assertTrue("FileStatus did not return the directory", - fileStatus.isDirectory()); + assertEquals("/d1/d2/d3/d4", fileStatus.getPath().toUri().getPath()); + assertTrue(fileStatus.isDirectory()); // invalid sub directory try { @@ -351,12 +337,12 @@ public void testMakeDirsWithAnExistingDirectoryPath() throws Exception { Path parent = new Path("/d1/d2/d3/d4/"); Path file1 = new Path(parent, "key1"); try (FSDataOutputStream outputStream = fs.create(file1, false)) { - assertNotNull("Should be able to create file", outputStream); + assertNotNull(outputStream, "Should be able to create file"); } Path subdir = new Path("/d1/d2/"); boolean status = fs.mkdirs(subdir); - assertTrue("Shouldn't send error if dir exists", status); + assertTrue(status, "Shouldn't send error if dir exists"); } @Test @@ -412,9 +398,8 @@ private void checkInvalidPath(Path path) { public void testOzoneFsServiceLoader() throws IOException { assumeFalse(FILE_SYSTEM_OPTIMIZED.equals(getBucketLayout())); - assertEquals( - FileSystem.getFileSystemClass(OzoneConsts.OZONE_URI_SCHEME, null), - OzoneFileSystem.class); + assertEquals(OzoneFileSystem.class, + FileSystem.getFileSystemClass(OzoneConsts.OZONE_URI_SCHEME, null)); } @Test @@ -435,10 +420,8 @@ public void testCreateDoesNotAddParentDirKeys() throws Exception { } // List status on the parent should show the child file - assertEquals("List status of parent should include the 1 child file", 1L, - fs.listStatus(parent).length); - assertTrue("Parent directory does not appear to be a directory", - fs.getFileStatus(parent).isDirectory()); + assertEquals(1L, fs.listStatus(parent).length, "List status of parent should include the 1 child file"); + assertTrue(fs.getFileStatus(parent).isDirectory(), "Parent directory does not appear to be a directory"); } @Test @@ -601,22 +584,19 @@ public void testListStatus() throws Exception { Path file2 = new Path(parent, "key2"); FileStatus[] fileStatuses = o3fs.listStatus(ROOT, EXCLUDE_TRASH); - assertEquals("Should be empty", 0, fileStatuses.length); + assertEquals(0, fileStatuses.length, "Should be empty"); ContractTestUtils.touch(fs, file1); ContractTestUtils.touch(fs, file2); fileStatuses = o3fs.listStatus(ROOT, EXCLUDE_TRASH); - assertEquals("Should have created parent", - 1, fileStatuses.length); - assertEquals("Parent path doesn't match", - fileStatuses[0].getPath().toUri().getPath(), parent.toString()); + assertEquals(1, fileStatuses.length, "Should have created parent"); + assertEquals(fileStatuses[0].getPath().toUri().getPath(), parent.toString(), "Parent path doesn't match"); // ListStatus on a directory should return all subdirs along with // files, even if there exists a file and sub-dir with the same name. fileStatuses = o3fs.listStatus(parent); - assertEquals("FileStatus did not return all children of the directory", - 2, fileStatuses.length); + assertEquals(2, fileStatuses.length, "FileStatus did not return all children of the directory"); // ListStatus should return only the immediate children of a directory. Path file3 = new Path(parent, "dir1/key3"); @@ -624,8 +604,7 @@ public void testListStatus() throws Exception { ContractTestUtils.touch(fs, file3); ContractTestUtils.touch(fs, file4); fileStatuses = o3fs.listStatus(parent); - assertEquals("FileStatus did not return all children of the directory", - 3, fileStatuses.length); + assertEquals(3, fileStatuses.length, "FileStatus did not return all children of the directory"); } @Test @@ -661,7 +640,7 @@ public void testListStatusWithIntermediateDir() throws Exception { FileStatus[] fileStatuses = fs.listStatus(ROOT, EXCLUDE_TRASH); // the number of immediate children of root is 1 - assertEquals(Arrays.toString(fileStatuses), 1, fileStatuses.length); + assertEquals(1, fileStatuses.length, Arrays.toString(fileStatuses)); writeClient.deleteKey(keyArgs); } @@ -694,8 +673,7 @@ public void testListStatusWithIntermediateDirWithECEnabled() FileStatus[] fileStatuses = fs.listStatus(ROOT, EXCLUDE_TRASH); // the number of immediate children of root is 1 assertEquals(1, fileStatuses.length); - assertEquals(fileStatuses[0].isErasureCoded(), - !bucketLayout.isFileSystemOptimized()); + assertEquals(fileStatuses[0].isErasureCoded(), !bucketLayout.isFileSystemOptimized()); fileStatuses = fs.listStatus(new Path( fileStatuses[0].getPath().toString() + "/object-name1")); assertEquals(1, fileStatuses.length); @@ -718,8 +696,7 @@ public void testListStatusOnRoot() throws Exception { // exist) and dir2 only. dir12 is not an immediate child of root and // hence should not be listed. FileStatus[] fileStatuses = o3fs.listStatus(ROOT, EXCLUDE_TRASH); - assertEquals("FileStatus should return only the immediate children", - 2, fileStatuses.length); + assertEquals(2, fileStatuses.length, "FileStatus should return only the immediate children"); // Verify that dir12 is not included in the result of the listStatus on root String fileStatus1 = fileStatuses[0].getPath().toUri().getPath(); @@ -767,9 +744,7 @@ public void testListStatusOnLargeDirectory() throws Exception { LOG.info("actualPathList: {}", actualPathList); } } - assertEquals( - "Total directories listed do not match the existing directories", - numDirs, fileStatuses.length); + assertEquals(numDirs, fileStatuses.length, "Total directories listed do not match the existing directories"); for (int i = 0; i < numDirs; i++) { assertTrue(paths.contains(fileStatuses[i].getPath().getName())); @@ -802,8 +777,7 @@ public void testListStatusOnKeyNameContainDelimiter() throws Exception { fileStatuses = fs.listStatus(new Path("/dir1/dir2")); assertEquals(1, fileStatuses.length); - assertEquals("/dir1/dir2/key1", - fileStatuses[0].getPath().toUri().getPath()); + assertEquals("/dir1/dir2/key1", fileStatuses[0].getPath().toUri().getPath()); assertTrue(fileStatuses[0].isFile()); } @@ -824,11 +798,11 @@ protected void deleteRootDir() throws IOException, InterruptedException { for (FileStatus fileStatus : fileStatuses) { LOG.error("Unexpected file, should have been deleted: {}", fileStatus); } - assertEquals("Delete root failed!", 0, fileStatuses.length); + assertEquals(0, fileStatuses.length, "Delete root failed!"); } } - private static void deleteRootRecursively(FileStatus[] fileStatuses) + private void deleteRootRecursively(FileStatus[] fileStatuses) throws IOException { for (FileStatus fStatus : fileStatuses) { fs.delete(fStatus.getPath(), true); @@ -861,8 +835,7 @@ public void testListStatusOnSubDirs() throws Exception { fs.mkdirs(dir2); FileStatus[] fileStatuses = o3fs.listStatus(dir1); - assertEquals("FileStatus should return only the immediate children", 2, - fileStatuses.length); + assertEquals(2, fileStatuses.length, "FileStatus should return only the immediate children"); // Verify that the two children of /dir1 returned by listStatus operation // are /dir1/dir11 and /dir1/dir12. @@ -894,8 +867,7 @@ public void testListStatusIteratorWithDir() throws Exception { while (it.hasNext()) { FileStatus fileStatus = it.next(); assertNotNull(fileStatus); - assertEquals("Parent path doesn't match", - fileStatus.getPath().toUri().getPath(), parent.toString()); + assertEquals(fileStatus.getPath().toUri().getPath(), parent.toString(), "Parent path doesn't match"); } // Iterator on a directory should return all subdirs along with // files, even if there exists a file and sub-dir with the same name. @@ -906,9 +878,7 @@ public void testListStatusIteratorWithDir() throws Exception { FileStatus fileStatus = it.next(); assertNotNull(fileStatus); } - assertEquals( - "Iterator did not return all the file status", - 2, iCount); + assertEquals(2, iCount, "Iterator did not return all the file status"); // Iterator should return file status for only the // immediate children of a directory. Path file3 = new Path(parent, "dir1/key3"); @@ -923,8 +893,8 @@ public void testListStatusIteratorWithDir() throws Exception { FileStatus fileStatus = it.next(); assertNotNull(fileStatus); } - assertEquals("Iterator did not return file status " + - "of all the children of the directory", 3, iCount); + assertEquals(3, iCount, "Iterator did not return file status " + + "of all the children of the directory"); } finally { // Cleanup @@ -955,11 +925,9 @@ public void testListStatusIteratorOnRoot() throws Exception { assertNotNull(fileStatus); // Verify that dir12 is not included in the result // of the listStatusIterator on root. - assertNotEquals(fileStatus.getPath().toUri().getPath(), - dir12.toString()); + assertNotEquals(fileStatus.getPath().toUri().getPath(), dir12.toString()); } - assertEquals("FileStatus should return only the immediate children", - 2, iCount); + assertEquals(2, iCount, "FileStatus should return only the immediate children"); } finally { // Cleanup fs.delete(dir2, true); @@ -1010,8 +978,7 @@ public void testListStatusIteratorOnSubDirs() throws Exception { equals(dir11.toString()) || fileStatus.getPath().toUri().getPath() .equals(dir12.toString())); } - assertEquals("FileStatus should return only the immediate children", 2, - iCount); + assertEquals(2, iCount, "FileStatus should return only the immediate children"); } finally { // Cleanup fs.delete(dir2, true); @@ -1035,8 +1002,7 @@ public void testSeekOnFileLength() throws IOException { fs.open(fileNotExists); fail("Should throw FileNotFoundException as file doesn't exist!"); } catch (FileNotFoundException fnfe) { - assertTrue("Expected KEY_NOT_FOUND error", - fnfe.getMessage().contains("KEY_NOT_FOUND")); + assertTrue(fnfe.getMessage().contains("KEY_NOT_FOUND"), "Expected KEY_NOT_FOUND error"); } } @@ -1059,14 +1025,12 @@ public void testAllocateMoreThanOneBlock() throws IOException { FileStatus fileStatus = fs.getFileStatus(file); long blkSize = fileStatus.getBlockSize(); long fileLength = fileStatus.getLen(); - assertTrue("Block allocation should happen", - fileLength > blkSize); + assertTrue(fileLength > blkSize, "Block allocation should happen"); long newNumBlockAllocations = cluster.getOzoneManager().getMetrics().getNumBlockAllocates(); - assertTrue("Block allocation should happen", - (newNumBlockAllocations > numBlockAllocationsOrg)); + assertTrue((newNumBlockAllocations > numBlockAllocationsOrg), "Block allocation should happen"); stream.seek(fileLength); assertEquals(-1, stream.read()); @@ -1097,11 +1061,10 @@ public void testNonExplicitlyCreatedPathExistsAfterItsLeafsWereRemoved() // after rename listStatus for interimPath should succeed and // interimPath should have no children FileStatus[] statuses = fs.listStatus(interimPath); - assertNotNull("liststatus returns a null array", statuses); - assertEquals("Statuses array is not empty", 0, statuses.length); + assertNotNull(statuses, "liststatus returns a null array"); + assertEquals(0, statuses.length, "Statuses array is not empty"); FileStatus fileStatus = fs.getFileStatus(interimPath); - assertEquals("FileStatus does not point to interimPath", - interimPath.getName(), fileStatus.getPath().getName()); + assertEquals(interimPath.getName(), fileStatus.getPath().getName(), "FileStatus does not point to interimPath"); } /** @@ -1120,8 +1083,7 @@ public void testRenameWithNonExistentSource() throws Exception { LOG.info("Created destin dir: {}", destin); LOG.info("Rename op-> source:{} to destin:{}}", source, destin); - assertFalse("Expected to fail rename as src doesn't exist", - fs.rename(source, destin)); + assertFalse(fs.rename(source, destin), "Expected to fail rename as src doesn't exist"); } /** @@ -1188,12 +1150,12 @@ public void testRenameToExistingDir() throws Exception { fs.mkdirs(acPath); // Rename from /a to /b. - assertTrue("Rename failed", fs.rename(aSourcePath, bDestinPath)); + assertTrue(fs.rename(aSourcePath, bDestinPath), "Rename failed"); final Path baPath = new Path(fs.getUri().toString() + "/b/a"); final Path bacPath = new Path(fs.getUri().toString() + "/b/a/c"); - assertTrue("Rename failed", fs.exists(baPath)); - assertTrue("Rename failed", fs.exists(bacPath)); + assertTrue(fs.exists(baPath), "Rename failed"); + assertTrue(fs.exists(bacPath), "Rename failed"); } /** @@ -1219,8 +1181,7 @@ public void testRenameToNewSubDirShouldNotExist() throws Exception { final Path baPath = new Path(fs.getUri().toString() + "/b/a/c"); fs.mkdirs(baPath); - assertFalse("New destin sub-path /b/a already exists", - fs.rename(aSourcePath, bDestinPath)); + assertFalse(fs.rename(aSourcePath, bDestinPath), "New destin sub-path /b/a already exists"); // Case-5.b) Rename file from /a/b/c/file1 to /a. // Should be failed since /a/file1 exists. @@ -1234,8 +1195,7 @@ public void testRenameToNewSubDirShouldNotExist() throws Exception { final Path aDestinPath = new Path(fs.getUri().toString() + "/a"); - assertFalse("New destin sub-path /b/a already exists", - fs.rename(abcFile1, aDestinPath)); + assertFalse(fs.rename(abcFile1, aDestinPath), "New destin sub-path /b/a already exists"); } /** @@ -1251,8 +1211,7 @@ public void testRenameDirToFile() throws Exception { ContractTestUtils.touch(fs, file1Destin); Path abcRootPath = new Path(fs.getUri().toString() + "/a/b/c"); fs.mkdirs(abcRootPath); - assertFalse("key already exists /root_dir/file1", - fs.rename(abcRootPath, file1Destin)); + assertFalse(fs.rename(abcRootPath, file1Destin), "key already exists /root_dir/file1"); } /** @@ -1268,18 +1227,18 @@ public void testRenameFile() throws Exception { + "/file1_Copy"); ContractTestUtils.touch(fs, file1Source); Path file1Destin = new Path(fs.getUri().toString() + root + "/file1"); - assertTrue("Renamed failed", fs.rename(file1Source, file1Destin)); - assertTrue("Renamed failed: /root/file1", fs.exists(file1Destin)); + assertTrue(fs.rename(file1Source, file1Destin), "Renamed failed"); + assertTrue(fs.exists(file1Destin), "Renamed failed: /root/file1"); - /** + /* * Reading several times, this is to verify that OmKeyInfo#keyName cached * entry is not modified. While reading back, OmKeyInfo#keyName will be * prepared and assigned to fullkeyPath name. */ for (int i = 0; i < 10; i++) { FileStatus[] fStatus = fs.listStatus(rootPath); - assertEquals("Renamed failed", 1, fStatus.length); - assertEquals("Wrong path name!", file1Destin, fStatus[0].getPath()); + assertEquals(1, fStatus.length, "Renamed failed"); + assertEquals(file1Destin, fStatus[0].getPath(), "Wrong path name!"); } } @@ -1296,9 +1255,9 @@ public void testRenameFileToDir() throws Exception { ContractTestUtils.touch(fs, file1Destin); Path abcRootPath = new Path(fs.getUri().toString() + "/a/b/c"); fs.mkdirs(abcRootPath); - assertTrue("Renamed failed", fs.rename(file1Destin, abcRootPath)); - assertTrue("Renamed filed: /a/b/c/file1", fs.exists(new Path(abcRootPath, - "file1"))); + assertTrue(fs.rename(file1Destin, abcRootPath), "Renamed failed"); + assertTrue(fs.exists(new Path(abcRootPath, + "file1")), "Renamed filed: /a/b/c/file1"); } @Test @@ -1374,18 +1333,16 @@ public void testRenameToParentDir() throws Exception { ContractTestUtils.touch(fs, file1Source); // rename source directory to its parent directory(destination). - assertTrue("Rename failed", fs.rename(dir2SourcePath, destRootPath)); + assertTrue(fs.rename(dir2SourcePath, destRootPath), "Rename failed"); final Path expectedPathAfterRename = new Path(fs.getUri().toString() + root + "/dir2"); - assertTrue("Rename failed", - fs.exists(expectedPathAfterRename)); + assertTrue(fs.exists(expectedPathAfterRename), "Rename failed"); // rename source file to its parent directory(destination). - assertTrue("Rename failed", fs.rename(file1Source, destRootPath)); + assertTrue(fs.rename(file1Source, destRootPath), "Rename failed"); final Path expectedFilePathAfterRename = new Path(fs.getUri().toString() + root + "/file2"); - assertTrue("Rename failed", - fs.exists(expectedFilePathAfterRename)); + assertTrue(fs.exists(expectedFilePathAfterRename), "Rename failed"); } @Test @@ -1399,11 +1356,10 @@ public void testRenameDir() throws Exception { LOG.info("Created dir {}", subdir); LOG.info("Will move {} to {}", source, dest); fs.rename(source, dest); - assertTrue("Directory rename failed", fs.exists(dest)); + assertTrue(fs.exists(dest), "Directory rename failed"); // Verify that the subdir is also renamed i.e. keys corresponding to the // sub-directories of the renamed directory have also been renamed. - assertTrue("Keys under the renamed directory not renamed", - fs.exists(new Path(dest, "sub_dir1"))); + assertTrue(fs.exists(new Path(dest, "sub_dir1")), "Keys under the renamed directory not renamed"); // Test if one path belongs to other FileSystem. IllegalArgumentException exception = assertThrows( @@ -1440,8 +1396,7 @@ public void testGetDirectoryModificationTime() FileStatus[] fileStatuses = o3fs.listStatus(mdir11); // Above listStatus result should only have one entry: mdir111 assertEquals(1, fileStatuses.length); - assertEquals(mdir111.toString(), - fileStatuses[0].getPath().toUri().getPath()); + assertEquals(mdir111.toString(), fileStatuses[0].getPath().toUri().getPath()); assertTrue(fileStatuses[0].isDirectory()); // The dir key is actually created on server, // so modification time should always be the same value. @@ -1457,8 +1412,7 @@ public void testGetDirectoryModificationTime() fileStatuses = o3fs.listStatus(mdir1); // Above listStatus result should only have one entry: mdir11 assertEquals(1, fileStatuses.length); - assertEquals(mdir11.toString(), - fileStatuses[0].getPath().toUri().getPath()); + assertEquals(mdir11.toString(), fileStatuses[0].getPath().toUri().getPath()); assertTrue(fileStatuses[0].isDirectory()); // Since the dir key doesn't exist on server, the modification time is // set to current time upon every listStatus request. @@ -1496,7 +1450,7 @@ public void testCreateKeyShouldUseRefreshedBucketReplicationConfig() // Set the fs.defaultFS and start the filesystem Configuration conf = new OzoneConfiguration(cluster.getConf()); - conf.set(CommonConfigurationKeysPublic.FS_DEFAULT_NAME_KEY, rootPath); + conf.set(FS_DEFAULT_NAME_KEY, rootPath); // Set the number of keys to be processed during batch operate. OzoneFileSystem o3FS = (OzoneFileSystem) FileSystem.get(conf); @@ -1538,9 +1492,8 @@ private void createKeyAndAssertKeyType(OzoneBucket bucket, OzoneFileSystem o3FS, Path keyPath, ReplicationType expectedType) throws IOException { o3FS.createFile(keyPath).build().close(); - assertEquals(expectedType.name(), - bucket.getKey(o3FS.pathToKey(keyPath)).getReplicationConfig() - .getReplicationType().name()); + assertEquals(expectedType.name(), bucket.getKey(o3FS.pathToKey(keyPath)).getReplicationConfig() + .getReplicationType().name()); } @Test @@ -1554,8 +1507,7 @@ public void testGetTrashRoots() throws IOException { fs.mkdirs(userTrash); res = o3fs.getTrashRoots(false); assertEquals(1, res.size()); - res.forEach(e -> assertEquals( - userTrash.toString(), e.getPath().toUri().getPath())); + res.forEach(e -> assertEquals(userTrash.toString(), e.getPath().toUri().getPath())); // Only have one user trash for now res = o3fs.getTrashRoots(true); assertEquals(1, res.size()); @@ -1572,8 +1524,7 @@ public void testGetTrashRoots() throws IOException { // allUsers = false should still return current user trash res = o3fs.getTrashRoots(false); assertEquals(1, res.size()); - res.forEach(e -> assertEquals( - userTrash.toString(), e.getPath().toUri().getPath())); + res.forEach(e -> assertEquals(userTrash.toString(), e.getPath().toUri().getPath())); // allUsers = true should return all user trash res = o3fs.getTrashRoots(true); assertEquals(6, res.size()); @@ -1663,8 +1614,7 @@ public void testListStatusOnLargeDirectoryForACLCheck() throws Exception { cluster.getOzoneManager().getKeyManager()); fail("Non-existent key name!"); } catch (OMException ome) { - assertEquals(OMException.ResultCodes.KEY_NOT_FOUND, - ome.getResult()); + assertEquals(OMException.ResultCodes.KEY_NOT_FOUND, ome.getResult()); } OzonePrefixPathImpl ozonePrefixPath = @@ -1678,7 +1628,7 @@ public void testListStatusOnLargeDirectoryForACLCheck() throws Exception { Iterator pathItr = ozonePrefixPath.getChildren(keyName); - assertTrue("Failed to list keyPath:" + keyName, pathItr.hasNext()); + assertTrue(pathItr.hasNext(), "Failed to list keyPath:" + keyName); Set actualPaths = new TreeSet<>(); while (pathItr.hasNext()) { @@ -1689,17 +1639,15 @@ public void testListStatusOnLargeDirectoryForACLCheck() throws Exception { Iterator subPathItr = ozonePrefixPath.getChildren(pathname); assertNotNull(subPathItr); - assertFalse("Failed to list keyPath: " + pathname, - subPathItr.hasNext()); + assertFalse(subPathItr.hasNext(), "Failed to list keyPath: " + pathname); } - assertEquals("ListStatus failed", paths.size(), - actualPaths.size()); + assertEquals(paths.size(), actualPaths.size(), "ListStatus failed"); for (String pathname : actualPaths) { paths.remove(pathname); } - assertTrue("ListStatus failed:" + paths, paths.isEmpty()); + assertTrue(paths.isEmpty(), "ListStatus failed:" + paths); } @Test @@ -1760,8 +1708,7 @@ public void testLoopInLinkBuckets() throws Exception { fail("Should throw Exception due to loop in Link Buckets"); } catch (OMException oe) { // Expected exception - assertEquals(OMException.ResultCodes.DETECTED_LOOP_IN_BUCKET_LINKS, - oe.getResult()); + assertEquals(OMException.ResultCodes.DETECTED_LOOP_IN_BUCKET_LINKS, oe.getResult()); } finally { volume.deleteBucket(linkBucket1Name); volume.deleteBucket(linkBucket2Name); @@ -1831,4 +1778,391 @@ public void testProcessingDetails() throws IOException, InterruptedException { GenericTestUtils.setLogLevel(log, Level.INFO); assertNotEquals(nonZeroLines, 0); } + + @Test + public void testOzFsReadWrite() throws IOException { + assumeFalse(FILE_SYSTEM_OPTIMIZED.equals(getBucketLayout())); + + long currentTime = Time.now(); + int stringLen = 20; + OMMetadataManager metadataManager = cluster.getOzoneManager() + .getMetadataManager(); + String lev1dir = "l1dir"; + Path lev1path = createPath("/" + lev1dir); + String lev1key = metadataManager.getOzoneDirKey(volumeName, bucketName, + o3fs.pathToKey(lev1path)); + String lev2dir = "l2dir"; + Path lev2path = createPath("/" + lev1dir + "/" + lev2dir); + String lev2key = metadataManager.getOzoneDirKey(volumeName, bucketName, + o3fs.pathToKey(lev2path)); + + String data = RandomStringUtils.randomAlphanumeric(stringLen); + String filePath = RandomStringUtils.randomAlphanumeric(5); + + Path path = createPath("/" + lev1dir + "/" + lev2dir + "/" + filePath); + String fileKey = metadataManager.getOzoneDirKey(volumeName, bucketName, + o3fs.pathToKey(path)); + + // verify prefix directories and the file, do not already exist + assertNull(metadataManager.getKeyTable(getBucketLayout()).get(lev1key)); + assertNull(metadataManager.getKeyTable(getBucketLayout()).get(lev2key)); + assertNull(metadataManager.getKeyTable(getBucketLayout()).get(fileKey)); + + Map statsBefore = statistics.snapshot(); + try (FSDataOutputStream stream = fs.create(path)) { + stream.writeBytes(data); + } + + assertChange(statsBefore, statistics, OP_CREATE, 1); + assertChange(statsBefore, statistics, "objects_created", 1); + + FileStatus status = fs.getFileStatus(path); + + assertChange(statsBefore, statistics, OP_GET_FILE_STATUS, 1); + assertChange(statsBefore, statistics, "objects_query", 1); + + // The timestamp of the newly created file should always be greater than + // the time when the test was started + assertTrue(status.getModificationTime() > currentTime); + + assertFalse(status.isDirectory()); + assertEquals(FsPermission.getFileDefault(), status.getPermission()); + verifyOwnerGroup(status); + + // verify prefix directories got created when creating the file. + assertEquals("l1dir/", metadataManager.getKeyTable(getBucketLayout()).get(lev1key).getKeyName()); + assertEquals("l1dir/l2dir/", metadataManager.getKeyTable(getBucketLayout()).get(lev2key).getKeyName()); + FileStatus lev1status = getDirectoryStat(lev1path); + assertNotNull(lev1status); + FileStatus lev2status = getDirectoryStat(lev2path); + assertNotNull(lev2status); + + try (FSDataInputStream inputStream = fs.open(path)) { + byte[] buffer = new byte[stringLen]; + // This read will not change the offset inside the file + int readBytes = inputStream.read(0, buffer, 0, buffer.length); + String out = new String(buffer, 0, buffer.length, UTF_8); + assertEquals(data, out); + assertEquals(readBytes, buffer.length); + assertEquals(0, inputStream.getPos()); + + // The following read will change the internal offset + readBytes = inputStream.read(buffer, 0, buffer.length); + assertEquals(data, out); + assertEquals(readBytes, buffer.length); + assertEquals(buffer.length, inputStream.getPos()); + } + + assertChange(statsBefore, statistics, OP_OPEN, 1); + assertChange(statsBefore, statistics, "objects_read", 1); + } + + private static void assertChange(Map before, OzoneFSStorageStatistics after, String key, long delta) { + assertEquals(before.get(key) + delta, after.getLong(key)); + } + + @Test + public void testReplication() throws IOException { + assumeFalse(FILE_SYSTEM_OPTIMIZED.equals(getBucketLayout())); + + int stringLen = 20; + String data = RandomStringUtils.randomAlphanumeric(stringLen); + String filePath = RandomStringUtils.randomAlphanumeric(5); + + Path pathIllegal = createPath("/" + filePath + "illegal"); + try (FSDataOutputStream streamIllegal = fs.create(pathIllegal, (short)2)) { + streamIllegal.writeBytes(data); + } + assertEquals(3, fs.getFileStatus(pathIllegal).getReplication()); + + Path pathLegal = createPath("/" + filePath + "legal"); + try (FSDataOutputStream streamLegal = fs.create(pathLegal, (short)1)) { + streamLegal.writeBytes(data); + } + assertEquals(1, fs.getFileStatus(pathLegal).getReplication()); + } + + private void verifyOwnerGroup(FileStatus fileStatus) { + String owner = getCurrentUser(); + assertEquals(owner, fileStatus.getOwner()); + assertEquals(owner, fileStatus.getGroup()); + } + + + @Test + public void testDirectory() throws IOException { + assumeFalse(FILE_SYSTEM_OPTIMIZED.equals(getBucketLayout())); + + String leafName = RandomStringUtils.randomAlphanumeric(5); + OMMetadataManager metadataManager = cluster.getOzoneManager() + .getMetadataManager(); + + String lev1dir = "abc"; + Path lev1path = createPath("/" + lev1dir); + String lev1key = metadataManager.getOzoneDirKey(volumeName, bucketName, + o3fs.pathToKey(lev1path)); + String lev2dir = "def"; + Path lev2path = createPath("/" + lev1dir + "/" + lev2dir); + String lev2key = metadataManager.getOzoneDirKey(volumeName, bucketName, + o3fs.pathToKey(lev2path)); + + Path leaf = createPath("/" + lev1dir + "/" + lev2dir + "/" + leafName); + String leafKey = metadataManager.getOzoneDirKey(volumeName, bucketName, + o3fs.pathToKey(leaf)); + + // verify prefix directories and the leaf, do not already exist + assertNull(metadataManager.getKeyTable(getBucketLayout()).get(lev1key)); + assertNull(metadataManager.getKeyTable(getBucketLayout()).get(lev2key)); + assertNull(metadataManager.getKeyTable(getBucketLayout()).get(leafKey)); + + assertTrue(fs.mkdirs(leaf)); + + // verify the leaf directory got created. + FileStatus leafstatus = getDirectoryStat(leaf); + assertNotNull(leafstatus); + + // verify prefix directories got created when creating the leaf directory. + assertEquals("abc/", metadataManager + .getKeyTable(getBucketLayout()) + .get(lev1key) + .getKeyName()); + assertEquals("abc/def/", metadataManager + .getKeyTable(getBucketLayout()) + .get(lev2key) + .getKeyName()); + FileStatus lev1status = getDirectoryStat(lev1path); + assertNotNull(lev1status); + FileStatus lev2status = getDirectoryStat(lev2path); + assertNotNull(lev2status); + + // check the root directory + FileStatus rootStatus = getDirectoryStat(createPath("/")); + assertNotNull(rootStatus); + + // root directory listing should contain the lev1 prefix directory + FileStatus[] statusList = fs.listStatus(createPath("/")); + assertEquals(1, statusList.length); + assertEquals(lev1status, statusList[0]); + } + + @Test + void testListStatus2() throws IOException { + List paths = new ArrayList<>(); + String dirPath = RandomStringUtils.randomAlphanumeric(5); + Path path = createPath("/" + dirPath); + paths.add(path); + + final Map initialStats = statistics.snapshot(); + assertTrue(fs.mkdirs(path)); + assertChange(initialStats, statistics, OP_MKDIRS, 1); + + final long initialListStatusCount = omMetrics.getNumListStatus(); + FileStatus[] statusList = fs.listStatus(createPath("/")); + assertEquals(1, statusList.length); + assertChange(initialStats, statistics, Statistic.OBJECTS_LIST.getSymbol(), 1); + assertEquals(initialListStatusCount + 1, omMetrics.getNumListStatus()); + assertEquals(fs.getFileStatus(path), statusList[0]); + + dirPath = RandomStringUtils.randomAlphanumeric(5); + path = createPath("/" + dirPath); + paths.add(path); + assertTrue(fs.mkdirs(path)); + assertChange(initialStats, statistics, OP_MKDIRS, 2); + + statusList = fs.listStatus(createPath("/")); + assertEquals(2, statusList.length); + assertChange(initialStats, statistics, Statistic.OBJECTS_LIST.getSymbol(), 2); + assertEquals(initialListStatusCount + 2, omMetrics.getNumListStatus()); + for (Path p : paths) { + assertTrue(Arrays.asList(statusList).contains(fs.getFileStatus(p))); + } + } + + @Test + void testOzoneManagerFileSystemInterface() throws IOException { + String dirPath = RandomStringUtils.randomAlphanumeric(5); + + Path path = createPath("/" + dirPath); + assertTrue(fs.mkdirs(path)); + + long numFileStatus = + cluster.getOzoneManager().getMetrics().getNumGetFileStatus(); + FileStatus status = fs.getFileStatus(path); + + assertEquals(numFileStatus + 1, + cluster.getOzoneManager().getMetrics().getNumGetFileStatus()); + assertTrue(status.isDirectory()); + assertEquals(FsPermission.getDirDefault(), status.getPermission()); + verifyOwnerGroup(status); + + long currentTime = System.currentTimeMillis(); + OmKeyArgs keyArgs = new OmKeyArgs.Builder() + .setVolumeName(volumeName) + .setBucketName(bucketName) + .setKeyName(o3fs.pathToKey(path)) + .build(); + OzoneFileStatus omStatus = + cluster.getOzoneManager().getFileStatus(keyArgs); + //Another get file status here, incremented the counter. + assertEquals(numFileStatus + 2, + cluster.getOzoneManager().getMetrics().getNumGetFileStatus()); + assertTrue(omStatus.isDirectory()); + + // For directories, the time returned is the current time when the dir key + // doesn't actually exist on server; if it exists, it will be a fixed value. + // In this case, the dir key exists. + assertEquals(0, omStatus.getKeyInfo().getDataSize()); + assertTrue(omStatus.getKeyInfo().getModificationTime() <= currentTime); + assertEquals(new Path(omStatus.getPath()).getName(), + o3fs.pathToKey(path)); + } + + @Test + public void testOzoneManagerLocatedFileStatus() throws IOException { + String data = RandomStringUtils.randomAlphanumeric(20); + String filePath = RandomStringUtils.randomAlphanumeric(5); + Path path = createPath("/" + filePath); + try (FSDataOutputStream stream = fs.create(path)) { + stream.writeBytes(data); + } + FileStatus status = fs.getFileStatus(path); + assertTrue(status instanceof LocatedFileStatus); + LocatedFileStatus locatedFileStatus = (LocatedFileStatus) status; + assertTrue(locatedFileStatus.getBlockLocations().length >= 1); + + for (BlockLocation blockLocation : locatedFileStatus.getBlockLocations()) { + assertTrue(blockLocation.getNames().length >= 1); + assertTrue(blockLocation.getHosts().length >= 1); + } + } + + @Test + void testBlockOffsetsWithMultiBlockFile() throws Exception { + // naive assumption: MiniOzoneCluster will not have larger than ~1GB + // block size when running this test. + int blockSize = (int) fs.getConf().getStorageSize( + OzoneConfigKeys.OZONE_SCM_BLOCK_SIZE, + OzoneConfigKeys.OZONE_SCM_BLOCK_SIZE_DEFAULT, + StorageUnit.BYTES + ); + String data = RandomStringUtils.randomAlphanumeric(2 * blockSize + 837); + String filePath = RandomStringUtils.randomAlphanumeric(5); + Path path = createPath("/" + filePath); + try (FSDataOutputStream stream = fs.create(path)) { + stream.writeBytes(data); + } + FileStatus status = fs.getFileStatus(path); + assertTrue(status instanceof LocatedFileStatus); + LocatedFileStatus locatedFileStatus = (LocatedFileStatus) status; + BlockLocation[] blockLocations = locatedFileStatus.getBlockLocations(); + + assertEquals(0, blockLocations[0].getOffset()); + assertEquals(blockSize, blockLocations[1].getOffset()); + assertEquals(2 * blockSize, blockLocations[2].getOffset()); + assertEquals(blockSize, blockLocations[0].getLength()); + assertEquals(blockSize, blockLocations[1].getLength()); + assertEquals(837, blockLocations[2].getLength()); + } + + @Test + void testPathToKey() { + assumeFalse(FILE_SYSTEM_OPTIMIZED.equals(getBucketLayout())); + + assertEquals("a/b/1", o3fs.pathToKey(new Path("/a/b/1"))); + + assertEquals("user/" + getCurrentUser() + "/key1/key2", + o3fs.pathToKey(new Path("key1/key2"))); + + assertEquals("key1/key2", + o3fs.pathToKey(new Path("o3fs://test1/key1/key2"))); + } + + + /** + * Verify that FS throws exception when trying to access bucket with + * incompatible layout. + */ + @Test + void testFileSystemWithObjectStoreLayout() throws IOException { + String obsVolume = UUID.randomUUID().toString(); + + try (OzoneClient client = cluster.newClient()) { + ObjectStore store = client.getObjectStore(); + + // Create volume and bucket + store.createVolume(obsVolume); + OzoneVolume volume = store.getVolume(obsVolume); + String obsBucket = UUID.randomUUID().toString(); + // create bucket with OBJECT_STORE bucket layout (incompatible with fs) + volume.createBucket(obsBucket, BucketArgs.newBuilder() + .setBucketLayout(BucketLayout.OBJECT_STORE) + .build()); + + String obsRootPath = String.format("%s://%s.%s/", + OzoneConsts.OZONE_URI_SCHEME, obsBucket, obsVolume); + + OzoneConfiguration config = new OzoneConfiguration(fs.getConf()); + config.set(FS_DEFAULT_NAME_KEY, obsRootPath); + + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> FileSystem.get(config)); + assertTrue(e.getMessage().contains("OBJECT_STORE, which does not support file system semantics")); + } + } + + private String getCurrentUser() { + try { + return UserGroupInformation.getCurrentUser().getShortUserName(); + } catch (IOException e) { + return OZONE_DEFAULT_USER; + } + } + + private Path createPath(String relativePath) { + if (enabledFileSystemPaths) { + return new Path(fsRoot + (relativePath.startsWith("/") ? "" : "/") + relativePath); + } else { + return new Path(relativePath); + } + } + + /** + * verify that a directory exists and is initialized correctly. + * @param path of the directory + * @return null indicates FILE_NOT_FOUND, else the FileStatus + */ + private FileStatus getDirectoryStat(Path path) throws IOException { + FileStatus status = fs.getFileStatus(path); + assertTrue(status.isDirectory()); + assertEquals(FsPermission.getDirDefault(), status.getPermission()); + verifyOwnerGroup(status); + assertEquals(0, status.getLen()); + return status; + } + + private void assertCounter(long value, String key) { + assertEquals(value, statistics.getLong(key).longValue()); + } + + @Test + void testSnapshotRead() throws Exception { + // Init data + Path snapPath1 = fs.createSnapshot(new Path("/"), "snap1"); + + Path file1 = new Path("/key1"); + Path file2 = new Path("/key2"); + ContractTestUtils.touch(fs, file1); + ContractTestUtils.touch(fs, file2); + Path snapPath2 = fs.createSnapshot(new Path("/"), "snap2"); + + Path file3 = new Path("/key3"); + ContractTestUtils.touch(fs, file3); + Path snapPath3 = fs.createSnapshot(new Path("/"), "snap3"); + + FileStatus[] f1 = fs.listStatus(snapPath1); + FileStatus[] f2 = fs.listStatus(snapPath2); + FileStatus[] f3 = fs.listStatus(snapPath3); + assertEquals(0, f1.length); + assertEquals(2, f2.length); + assertEquals(3, f3.length); + } } diff --git a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestOzoneFileSystemWithFSO.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/AbstractOzoneFileSystemTestWithFSO.java similarity index 80% rename from hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestOzoneFileSystemWithFSO.java rename to hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/AbstractOzoneFileSystemTestWithFSO.java index d2d2fd6b816c..cdb7d0cc16e9 100644 --- a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestOzoneFileSystemWithFSO.java +++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/AbstractOzoneFileSystemTestWithFSO.java @@ -33,61 +33,41 @@ import org.apache.hadoop.ozone.om.helpers.OmDirectoryInfo; import org.apache.hadoop.ozone.om.helpers.OmKeyInfo; import org.apache.ozone.test.GenericTestUtils; -import org.junit.After; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestMethodOrder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThrows; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; /** * Ozone file system tests that are not covered by contract tests, * - prefix layout. * */ -@RunWith(Parameterized.class) -public class TestOzoneFileSystemWithFSO extends TestOzoneFileSystem { - - @Parameterized.Parameters - public static Collection data() { - return Arrays.asList( - new Object[]{true, true}, - new Object[]{true, false}); - } - - @BeforeClass - public static void init() { - setBucketLayout(BucketLayout.FILE_SYSTEM_OPTIMIZED); - } +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +abstract class AbstractOzoneFileSystemTestWithFSO extends AbstractOzoneFileSystemTest { - public TestOzoneFileSystemWithFSO(boolean setDefaultFs, - boolean enableOMRatis) { - super(setDefaultFs, enableOMRatis); - } + private static final Logger LOG = + LoggerFactory.getLogger(AbstractOzoneFileSystemTestWithFSO.class); - @After - @Override - public void cleanup() { - super.cleanup(); + AbstractOzoneFileSystemTestWithFSO(boolean enableOMRatis) { + super(true, enableOMRatis, BucketLayout.FILE_SYSTEM_OPTIMIZED); } - private static final Logger LOG = - LoggerFactory.getLogger(TestOzoneFileSystemWithFSO.class); - @Test public void testListStatusWithoutRecursiveSearch() throws Exception { /* @@ -102,32 +82,27 @@ public void testListStatusWithoutRecursiveSearch() throws Exception { Path key1 = new Path("/key1"); try (FSDataOutputStream outputStream = getFs().create(key1, false)) { - assertNotNull("Should be able to create file: key1", - outputStream); + assertNotNull(outputStream, "Should be able to create file: key1"); } Path d1 = new Path("/d1"); Path dir1Key1 = new Path(d1, "key1"); try (FSDataOutputStream outputStream = getFs().create(dir1Key1, false)) { - assertNotNull("Should be able to create file: " + dir1Key1, - outputStream); + assertNotNull(outputStream, "Should be able to create file: " + dir1Key1); } Path d2 = new Path("/d2"); Path dir2Key1 = new Path(d2, "key1"); try (FSDataOutputStream outputStream = getFs().create(dir2Key1, false)) { - assertNotNull("Should be able to create file: " + dir2Key1, - outputStream); + assertNotNull(outputStream, "Should be able to create file: " + dir2Key1); } Path dir1Dir2 = new Path("/d1/d2/"); Path dir1Dir2Key1 = new Path(dir1Dir2, "key1"); try (FSDataOutputStream outputStream = getFs().create(dir1Dir2Key1, false)) { - assertNotNull("Should be able to create file: " + dir1Dir2Key1, - outputStream); + assertNotNull(outputStream, "Should be able to create file: " + dir1Dir2Key1); } Path d1Key2 = new Path(d1, "key2"); try (FSDataOutputStream outputStream = getFs().create(d1Key2, false)) { - assertNotNull("Should be able to create file: " + d1Key2, - outputStream); + assertNotNull(outputStream, "Should be able to create file: " + d1Key2); } Path dir1Dir3 = new Path("/d1/d3/"); @@ -141,8 +116,7 @@ public void testListStatusWithoutRecursiveSearch() throws Exception { // Root Directory FileStatus[] fileStatusList = getFs().listStatus(new Path("/")); - assertEquals("FileStatus should return files and directories", - 3, fileStatusList.length); + assertEquals(3, fileStatusList.length, "FileStatus should return files and directories"); ArrayList expectedPaths = new ArrayList<>(); expectedPaths.add("o3fs://" + bucketName + "." + volumeName + "/d1"); expectedPaths.add("o3fs://" + bucketName + "." + volumeName + "/d2"); @@ -150,13 +124,11 @@ public void testListStatusWithoutRecursiveSearch() throws Exception { for (FileStatus fileStatus : fileStatusList) { expectedPaths.remove(fileStatus.getPath().toString()); } - assertEquals("Failed to return the filestatus[]" + expectedPaths, - 0, expectedPaths.size()); + assertEquals(0, expectedPaths.size(), "Failed to return the filestatus[]" + expectedPaths); // level-1 sub-dirs fileStatusList = getFs().listStatus(new Path("/d1")); - assertEquals("FileStatus should return files and directories", - 5, fileStatusList.length); + assertEquals(5, fileStatusList.length, "FileStatus should return files and directories"); expectedPaths = new ArrayList<>(); expectedPaths.add("o3fs://" + bucketName + "." + volumeName + "/d1/d2"); expectedPaths.add("o3fs://" + bucketName + "." + volumeName + "/d1/d3"); @@ -166,34 +138,29 @@ public void testListStatusWithoutRecursiveSearch() throws Exception { for (FileStatus fileStatus : fileStatusList) { expectedPaths.remove(fileStatus.getPath().toString()); } - assertEquals("Failed to return the filestatus[]" + expectedPaths, - 0, expectedPaths.size()); + assertEquals(0, expectedPaths.size(), "Failed to return the filestatus[]" + expectedPaths); // level-2 sub-dirs fileStatusList = getFs().listStatus(new Path("/d1/d2")); - assertEquals("FileStatus should return files and directories", - 1, fileStatusList.length); + assertEquals(1, fileStatusList.length, "FileStatus should return files and directories"); expectedPaths = new ArrayList<>(); expectedPaths.add("o3fs://" + bucketName + "." + volumeName + "/d1/d2/" + "key1"); for (FileStatus fileStatus : fileStatusList) { expectedPaths.remove(fileStatus.getPath().toString()); } - assertEquals("Failed to return the filestatus[]" + expectedPaths, - 0, expectedPaths.size()); + assertEquals(0, expectedPaths.size(), "Failed to return the filestatus[]" + expectedPaths); // level-2 key2 fileStatusList = getFs().listStatus(new Path("/d1/d2/key1")); - assertEquals("FileStatus should return files and directories", - 1, fileStatusList.length); + assertEquals(1, fileStatusList.length, "FileStatus should return files and directories"); expectedPaths = new ArrayList<>(); expectedPaths.add("o3fs://" + bucketName + "." + volumeName + "/d1/d2/" + "key1"); for (FileStatus fileStatus : fileStatusList) { expectedPaths.remove(fileStatus.getPath().toString()); } - assertEquals("Failed to return the filestatus[]" + expectedPaths, - 0, expectedPaths.size()); + assertEquals(0, expectedPaths.size(), "Failed to return the filestatus[]" + expectedPaths); // invalid root key try { @@ -222,24 +189,21 @@ public void testListFilesRecursive() throws Exception { Path dir1Dir1Dir2Key1 = new Path("/d1/d1/d2/key1"); try (FSDataOutputStream outputStream = getFs().create(dir1Dir1Dir2Key1, false)) { - assertNotNull("Should be able to create file: " + dir1Dir1Dir2Key1, - outputStream); + assertNotNull(outputStream, "Should be able to create file: " + dir1Dir1Dir2Key1); } Path key1 = new Path("/key1"); try (FSDataOutputStream outputStream = getFs().create(key1, false)) { - assertNotNull("Should be able to create file: " + key1, - outputStream); + assertNotNull(outputStream, "Should be able to create file: " + key1); } Path key2 = new Path("/key2"); try (FSDataOutputStream outputStream = getFs().create(key2, false)) { - assertNotNull("Should be able to create file: key2", - outputStream); + assertNotNull(outputStream, "Should be able to create file: key2"); } Path dir1Dir2Dir1Dir2Key1 = new Path("/d1/d2/d1/d2/key1"); try (FSDataOutputStream outputStream = getFs().create(dir1Dir2Dir1Dir2Key1, false)) { - assertNotNull("Should be able to create file: " - + dir1Dir2Dir1Dir2Key1, outputStream); + assertNotNull(outputStream, "Should be able to create file: " + + dir1Dir2Dir1Dir2Key1); } RemoteIterator fileStatusItr = getFs().listFiles( new Path("/"), true); @@ -256,10 +220,8 @@ public void testListFilesRecursive() throws Exception { expectedPaths.remove(status.getPath().toString()); actualCount++; } - assertEquals("Failed to get all the files: " + expectedPaths, - expectedFilesCount, actualCount); - assertEquals("Failed to get all the files: " + expectedPaths, 0, - expectedPaths.size()); + assertEquals(expectedFilesCount, actualCount, "Failed to get all the files: " + expectedPaths); + assertEquals(0, expectedPaths.size(), "Failed to get all the files: " + expectedPaths); // Recursive=false fileStatusItr = getFs().listFiles(new Path("/"), false); @@ -273,10 +235,8 @@ public void testListFilesRecursive() throws Exception { expectedPaths.remove(status.getPath().toString()); actualCount++; } - assertEquals("Failed to get all the files: " + expectedPaths, 0, - expectedPaths.size()); - assertEquals("Failed to get all the files: " + expectedPaths, - expectedFilesCount, actualCount); + assertEquals(0, expectedPaths.size(), "Failed to get all the files: " + expectedPaths); + assertEquals(expectedFilesCount, actualCount, "Failed to get all the files: " + expectedPaths); } /** @@ -431,8 +391,7 @@ public void testMultiLevelDirs() throws Exception { // reset metrics long numKeys = getCluster().getOzoneManager().getMetrics().getNumKeys(); getCluster().getOzoneManager().getMetrics().decNumKeys(numKeys); - assertEquals(0, - getCluster().getOzoneManager().getMetrics().getNumKeys()); + assertEquals(0, getCluster().getOzoneManager().getMetrics().getNumKeys()); // Op 1. create dir -> /d1/d2/d3/d4/ // Op 2. create dir -> /d1/d2/d3/d4/d5 @@ -444,7 +403,7 @@ public void testMultiLevelDirs() throws Exception { getCluster().getOzoneManager().getMetadataManager(); OmBucketInfo omBucketInfo = omMgr.getBucketTable() .get(omMgr.getBucketKey(getVolumeName(), getBucketName())); - assertNotNull("Failed to find bucketInfo", omBucketInfo); + assertNotNull(omBucketInfo, "Failed to find bucketInfo"); final long volumeId = omMgr.getVolumeId(getVolumeName()); final long bucketId = omMgr.getBucketId(getVolumeName(), getBucketName()); @@ -462,8 +421,7 @@ public void testMultiLevelDirs() throws Exception { verifyDirKey(volumeId, bucketId, d3ObjectID, "d4", "/d1/d2/d3/d4", dirKeys, omMgr); - assertEquals("Wrong OM numKeys metrics", 4, - getCluster().getOzoneManager().getMetrics().getNumKeys()); + assertEquals(4, getCluster().getOzoneManager().getMetrics().getNumKeys(), "Wrong OM numKeys metrics"); // create sub-dirs under same parent Path subDir5 = new Path("/d1/d2/d3/d4/d5"); @@ -476,15 +434,14 @@ public void testMultiLevelDirs() throws Exception { long d6ObjectID = verifyDirKey(volumeId, bucketId, d4ObjectID, "d6", "/d1/d2/d3/d4/d6", dirKeys, omMgr); - assertTrue( - "Wrong objectIds for sub-dirs[" + d5ObjectID + "/d5, " + d6ObjectID - + "/d6] of same parent!", d5ObjectID != d6ObjectID); + assertTrue(d5ObjectID != d6ObjectID, "Wrong objectIds for sub-dirs[" + d5ObjectID + "/d5, " + d6ObjectID + + "/d6] of same parent!"); - assertEquals("Wrong OM numKeys metrics", 6, - getCluster().getOzoneManager().getMetrics().getNumKeys()); + assertEquals(6, getCluster().getOzoneManager().getMetrics().getNumKeys(), "Wrong OM numKeys metrics"); } @Test + @Order(1) public void testCreateFile() throws Exception { // Op 1. create dir -> /d1/d2/d3/d4/ Path parent = new Path("/d1/d2/"); @@ -496,7 +453,7 @@ public void testCreateFile() throws Exception { getCluster().getOzoneManager().getMetadataManager(); OmBucketInfo omBucketInfo = omMgr.getBucketTable() .get(omMgr.getBucketKey(getVolumeName(), getBucketName())); - assertNotNull("Failed to find bucketInfo", omBucketInfo); + assertNotNull(omBucketInfo, "Failed to find bucketInfo"); ArrayList dirKeys = new ArrayList<>(); @@ -516,7 +473,7 @@ public void testCreateFile() throws Exception { outputStream.close(); OmKeyInfo omKeyInfo = omMgr.getKeyTable(getBucketLayout()).get(openFileKey); - assertNotNull("Invalid Key!", omKeyInfo); + assertNotNull(omKeyInfo, "Invalid Key!"); verifyOMFileInfoFormat(omKeyInfo, file.getName(), d2ObjectID); // wait for DB updates @@ -538,7 +495,7 @@ public void testCreateFile() throws Exception { @Test public void testLeaseRecoverable() throws Exception { // Create a file - Path parent = new Path("/d1/d2/"); + Path parent = new Path("/d1"); Path source = new Path(parent, "file1"); LeaseRecoverable fs = (LeaseRecoverable)getFs(); @@ -571,11 +528,10 @@ public void testFSDeleteLogWarnNoExist() throws Exception { private void verifyOMFileInfoFormat(OmKeyInfo omKeyInfo, String fileName, long parentID) { - assertEquals("Wrong keyName", fileName, omKeyInfo.getKeyName()); - assertEquals("Wrong parentID", parentID, - omKeyInfo.getParentObjectID()); + assertEquals(fileName, omKeyInfo.getKeyName(), "Wrong keyName"); + assertEquals(parentID, omKeyInfo.getParentObjectID(), "Wrong parentID"); String dbKey = parentID + OzoneConsts.OM_KEY_PREFIX + fileName; - assertEquals("Wrong path format", dbKey, omKeyInfo.getPath()); + assertEquals(dbKey, omKeyInfo.getPath(), "Wrong path format"); } long verifyDirKey(long volumeId, long bucketId, long parentId, @@ -586,21 +542,13 @@ long verifyDirKey(long volumeId, long bucketId, long parentId, parentId + "/" + dirKey; dirKeys.add(dbKey); OmDirectoryInfo dirInfo = omMgr.getDirectoryTable().get(dbKey); - assertNotNull("Failed to find " + absolutePath + - " using dbKey: " + dbKey, dirInfo); - assertEquals("Parent Id mismatches", parentId, - dirInfo.getParentObjectID()); - assertEquals("Mismatches directory name", dirKey, - dirInfo.getName()); - assertTrue("Mismatches directory creation time param", - dirInfo.getCreationTime() > 0); - assertEquals("Mismatches directory modification time param", - dirInfo.getCreationTime(), dirInfo.getModificationTime()); + assertNotNull(dirInfo, "Failed to find " + absolutePath + + " using dbKey: " + dbKey); + assertEquals(parentId, dirInfo.getParentObjectID(), "Parent Id mismatches"); + assertEquals(dirKey, dirInfo.getName(), "Mismatches directory name"); + assertTrue(dirInfo.getCreationTime() > 0, "Mismatches directory creation time param"); + assertEquals(dirInfo.getCreationTime(), dirInfo.getModificationTime()); return dirInfo.getObjectID(); } - @Override - public BucketLayout getBucketLayout() { - return BucketLayout.FILE_SYSTEM_OPTIMIZED; - } } diff --git a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestOzoneFileInterfacesWithFSO.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestO3FS.java similarity index 54% rename from hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestOzoneFileInterfacesWithFSO.java rename to hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestO3FS.java index 5a0dc6ef5a2f..5fdab6fe95da 100644 --- a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestOzoneFileInterfacesWithFSO.java +++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestO3FS.java @@ -6,39 +6,23 @@ * 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 - * + *

+ * 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.fs.ozone; import org.apache.hadoop.ozone.om.helpers.BucketLayout; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - -/** - * Test OzoneFileSystem Interfaces - prefix layout. - * - * This test will test the various interfaces i.e. - * create, read, write, getFileStatus - */ -@RunWith(Parameterized.class) -public class TestOzoneFileInterfacesWithFSO extends TestOzoneFileInterfaces { - - public TestOzoneFileInterfacesWithFSO(boolean setDefaultFs, - boolean useAbsolutePath, boolean enabledFileSystemPaths) - throws Exception { - super(setDefaultFs, useAbsolutePath, enabledFileSystemPaths); - } +import org.junit.jupiter.api.TestInstance; - @Override - public BucketLayout getBucketLayout() { - return BucketLayout.FILE_SYSTEM_OPTIMIZED; +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class TestO3FS extends AbstractOzoneFileSystemTest { + TestO3FS() { + super(false, false, BucketLayout.LEGACY); } } diff --git a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestO3FSWithFSO.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestO3FSWithFSO.java new file mode 100644 index 000000000000..0d6be62b4fc4 --- /dev/null +++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestO3FSWithFSO.java @@ -0,0 +1,27 @@ +/* + * 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.fs.ozone; + +import org.junit.jupiter.api.TestInstance; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class TestO3FSWithFSO extends AbstractOzoneFileSystemTestWithFSO { + TestO3FSWithFSO() { + super(false); + } +} diff --git a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestO3FSWithFSOAndOMRatis.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestO3FSWithFSOAndOMRatis.java new file mode 100644 index 000000000000..d616d08e3287 --- /dev/null +++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestO3FSWithFSOAndOMRatis.java @@ -0,0 +1,27 @@ +/* + * 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.fs.ozone; + +import org.junit.jupiter.api.TestInstance; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class TestO3FSWithFSOAndOMRatis extends AbstractOzoneFileSystemTestWithFSO { + TestO3FSWithFSOAndOMRatis() { + super(true); + } +} diff --git a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestO3FSWithFSPaths.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestO3FSWithFSPaths.java new file mode 100644 index 000000000000..5fffd9df7f46 --- /dev/null +++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestO3FSWithFSPaths.java @@ -0,0 +1,28 @@ +/* + * 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.fs.ozone; + +import org.apache.hadoop.ozone.om.helpers.BucketLayout; +import org.junit.jupiter.api.TestInstance; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class TestO3FSWithFSPaths extends AbstractOzoneFileSystemTest { + TestO3FSWithFSPaths() { + super(true, false, BucketLayout.LEGACY); + } +} diff --git a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestO3FSWithFSPathsAndOMRatis.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestO3FSWithFSPathsAndOMRatis.java new file mode 100644 index 000000000000..461961c3e73b --- /dev/null +++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestO3FSWithFSPathsAndOMRatis.java @@ -0,0 +1,28 @@ +/* + * 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.fs.ozone; + +import org.apache.hadoop.ozone.om.helpers.BucketLayout; +import org.junit.jupiter.api.TestInstance; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class TestO3FSWithFSPathsAndOMRatis extends AbstractOzoneFileSystemTest { + TestO3FSWithFSPathsAndOMRatis() { + super(true, true, BucketLayout.LEGACY); + } +} diff --git a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestO3FSWithOMRatis.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestO3FSWithOMRatis.java new file mode 100644 index 000000000000..a02f3812e04d --- /dev/null +++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestO3FSWithOMRatis.java @@ -0,0 +1,28 @@ +/* + * 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.fs.ozone; + +import org.apache.hadoop.ozone.om.helpers.BucketLayout; +import org.junit.jupiter.api.TestInstance; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class TestO3FSWithOMRatis extends AbstractOzoneFileSystemTest { + TestO3FSWithOMRatis() { + super(false, true, BucketLayout.LEGACY); + } +} diff --git a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestOzoneFileInterfaces.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestOzoneFileInterfaces.java deleted file mode 100644 index c735e312aac9..000000000000 --- a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestOzoneFileInterfaces.java +++ /dev/null @@ -1,647 +0,0 @@ -/* - * 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.fs.ozone; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.net.URI; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.UUID; - -import org.apache.hadoop.conf.StorageUnit; -import org.apache.hadoop.fs.BlockLocation; -import org.apache.hadoop.fs.FSDataInputStream; -import org.apache.hadoop.fs.FSDataOutputStream; -import org.apache.hadoop.fs.FileStatus; -import org.apache.hadoop.fs.FileSystem; -import org.apache.hadoop.fs.GlobalStorageStatistics; -import org.apache.hadoop.fs.LocatedFileStatus; -import org.apache.hadoop.fs.Path; -import org.apache.hadoop.fs.StorageStatistics; -import org.apache.hadoop.fs.permission.FsPermission; -import org.apache.hadoop.hdds.conf.OzoneConfiguration; -import org.apache.hadoop.ozone.MiniOzoneCluster; -import org.apache.hadoop.ozone.OzoneConfigKeys; -import org.apache.hadoop.ozone.OzoneConsts; -import org.apache.hadoop.ozone.TestDataUtil; -import org.apache.hadoop.ozone.client.BucketArgs; -import org.apache.hadoop.ozone.client.ObjectStore; -import org.apache.hadoop.ozone.client.OzoneClient; -import org.apache.hadoop.ozone.client.OzoneVolume; -import org.apache.hadoop.ozone.om.OMConfigKeys; -import org.apache.hadoop.ozone.om.OMMetadataManager; -import org.apache.hadoop.ozone.om.OMMetrics; -import org.apache.hadoop.ozone.om.helpers.BucketLayout; -import org.apache.hadoop.ozone.om.helpers.OmKeyArgs; -import org.apache.hadoop.ozone.om.helpers.OzoneFileStatus; -import org.apache.hadoop.security.UserGroupInformation; -import org.apache.hadoop.util.Time; - -import static java.nio.charset.StandardCharsets.UTF_8; -import org.apache.hadoop.hdds.utils.IOUtils; -import org.apache.commons.lang3.RandomStringUtils; - -import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.FS_DEFAULT_NAME_KEY; -import static org.apache.hadoop.fs.ozone.Constants.OZONE_DEFAULT_USER; - -import org.junit.After; -import org.junit.AfterClass; - -import static org.apache.hadoop.ozone.om.helpers.BucketLayout.FILE_SYSTEM_OPTIMIZED; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.junit.Assume.assumeFalse; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestRule; -import org.junit.rules.Timeout; -import org.apache.ozone.test.JUnit5AwareTimeout; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; - -/** - * Test OzoneFileSystem Interfaces. - * - * This test will test the various interfaces i.e. - * create, read, write, getFileStatus - */ -@RunWith(Parameterized.class) -public class TestOzoneFileInterfaces { - - /** - * Set a timeout for each test. - */ - @Rule - public TestRule timeout = new JUnit5AwareTimeout(Timeout.seconds(300)); - - private String rootPath; - - /** - * Parameter class to set absolute url/defaultFS handling. - *

- * Hadoop file systems could be used in multiple ways: Using the defaultfs - * and file path without the schema, or use absolute url-s even with - * different defaultFS. This parameter matrix would test both the use cases. - */ - @Parameters - public static Collection data() { - return Arrays.asList(new Object[][] {{false, true, true}, - {true, false, false}}); - } - - private static boolean setDefaultFs; - - private static boolean useAbsolutePath; - - private static MiniOzoneCluster cluster = null; - - private FileSystem fs; - - private OzoneFileSystem o3fs; - - private String volumeName; - - private String bucketName; - - private OzoneFSStorageStatistics statistics; - - private OMMetrics omMetrics; - - private static boolean enableFileSystemPaths; - - @SuppressWarnings("checkstyle:VisibilityModifier") - protected boolean enableFileSystemPathsInstance; - - public TestOzoneFileInterfaces(boolean setDefaultFs, - boolean useAbsolutePath, boolean enabledFileSystemPaths) - throws Exception { - enableFileSystemPathsInstance = enabledFileSystemPaths; - if (this.setDefaultFs != setDefaultFs - || this.useAbsolutePath != useAbsolutePath - || this.enableFileSystemPaths != enabledFileSystemPaths) { - setParameters(setDefaultFs, useAbsolutePath, enabledFileSystemPaths); - teardown(); - init(); - } - GlobalStorageStatistics.INSTANCE.reset(); - } - - private static void setParameters(boolean defaultFs, - boolean absolutePath, - boolean fileSystemPaths) { - setDefaultFs = defaultFs; - useAbsolutePath = absolutePath; - enableFileSystemPaths = fileSystemPaths; - } - - private static void setCluster(MiniOzoneCluster newCluster) { - cluster = newCluster; - } - - public void init() throws Exception { - OzoneConfiguration conf = getOzoneConfiguration(); - conf.set(OMConfigKeys.OZONE_DEFAULT_BUCKET_LAYOUT, - BucketLayout.LEGACY.name()); - MiniOzoneCluster newCluster = MiniOzoneCluster.newBuilder(conf) - .setNumDatanodes(3) - .build(); - newCluster.waitForClusterToBeReady(); - setCluster(newCluster); - } - - @Before - public void setupTest() throws Exception { - volumeName = RandomStringUtils.randomAlphabetic(10).toLowerCase(); - bucketName = RandomStringUtils.randomAlphabetic(10).toLowerCase(); - - OzoneConfiguration conf = cluster.getConf(); - - // create a volume and a bucket to be used by OzoneFileSystem - try (OzoneClient client = cluster.newClient()) { - TestDataUtil.createVolumeAndBucket(client, volumeName, bucketName, - getBucketLayout()); - } - - rootPath = String - .format("%s://%s.%s/", OzoneConsts.OZONE_URI_SCHEME, bucketName, - volumeName); - if (setDefaultFs) { - // Set the fs.defaultFS and start the filesystem - conf.set(FS_DEFAULT_NAME_KEY, rootPath); - fs = FileSystem.get(conf); - } else { - fs = FileSystem.get(new URI(rootPath + "/test.txt"), conf); - } - o3fs = (OzoneFileSystem) fs; - statistics = (OzoneFSStorageStatistics) o3fs.getOzoneFSOpsCountStatistics(); - omMetrics = cluster.getOzoneManager().getMetrics(); - } - - protected OzoneConfiguration getOzoneConfiguration() { - OzoneConfiguration conf = new OzoneConfiguration(); - conf.setBoolean(OMConfigKeys.OZONE_OM_ENABLE_FILESYSTEM_PATHS, - enableFileSystemPaths); - return conf; - } - - @After - public void closeFs() throws IOException { - IOUtils.closeQuietly(fs); - } - - @AfterClass - public static void teardown() throws IOException { - if (cluster != null) { - cluster.shutdown(); - } - } - - @Test - public void testFileSystemInit() throws IOException { - assumeFalse(FILE_SYSTEM_OPTIMIZED.equals(getBucketLayout())); - - if (setDefaultFs) { - assertTrue( - "The initialized file system is not OzoneFileSystem but " + - fs.getClass(), - fs instanceof OzoneFileSystem); - assertEquals(OzoneConsts.OZONE_URI_SCHEME, fs.getUri().getScheme()); - assertEquals(OzoneConsts.OZONE_URI_SCHEME, statistics.getScheme()); - } - } - - @Test - public void testOzFsReadWrite() throws IOException { - assumeFalse(FILE_SYSTEM_OPTIMIZED.equals(getBucketLayout())); - - long currentTime = Time.now(); - int stringLen = 20; - OMMetadataManager metadataManager = cluster.getOzoneManager() - .getMetadataManager(); - String lev1dir = "l1dir"; - Path lev1path = createPath("/" + lev1dir); - String lev1key = metadataManager.getOzoneDirKey(volumeName, bucketName, - o3fs.pathToKey(lev1path)); - String lev2dir = "l2dir"; - Path lev2path = createPath("/" + lev1dir + "/" + lev2dir); - String lev2key = metadataManager.getOzoneDirKey(volumeName, bucketName, - o3fs.pathToKey(lev2path)); - - String data = RandomStringUtils.randomAlphanumeric(stringLen); - String filePath = RandomStringUtils.randomAlphanumeric(5); - - Path path = createPath("/" + lev1dir + "/" + lev2dir + "/" + filePath); - String fileKey = metadataManager.getOzoneDirKey(volumeName, bucketName, - o3fs.pathToKey(path)); - - // verify prefix directories and the file, do not already exist - assertTrue( - metadataManager.getKeyTable(getBucketLayout()).get(lev1key) == null); - assertTrue( - metadataManager.getKeyTable(getBucketLayout()).get(lev2key) == null); - assertTrue( - metadataManager.getKeyTable(getBucketLayout()).get(fileKey) == null); - - try (FSDataOutputStream stream = fs.create(path)) { - stream.writeBytes(data); - } - - assertEquals(statistics.getLong( - StorageStatistics.CommonStatisticNames.OP_CREATE).longValue(), 1); - assertEquals(statistics.getLong("objects_created").longValue(), 1); - - FileStatus status = fs.getFileStatus(path); - assertEquals(statistics.getLong( - StorageStatistics.CommonStatisticNames.OP_GET_FILE_STATUS).longValue(), - 1); - assertEquals(statistics.getLong("objects_query").longValue(), 1); - // The timestamp of the newly created file should always be greater than - // the time when the test was started - assertTrue("Modification time has not been recorded: " + status, - status.getModificationTime() > currentTime); - - assertFalse(status.isDirectory()); - assertEquals(FsPermission.getFileDefault(), status.getPermission()); - verifyOwnerGroup(status); - - FileStatus lev1status; - FileStatus lev2status; - - // verify prefix directories got created when creating the file. - assertTrue( - metadataManager.getKeyTable(getBucketLayout()).get(lev1key).getKeyName() - .equals("l1dir/")); - assertTrue( - metadataManager.getKeyTable(getBucketLayout()).get(lev2key).getKeyName() - .equals("l1dir/l2dir/")); - lev1status = getDirectoryStat(lev1path); - lev2status = getDirectoryStat(lev2path); - assertTrue((lev1status != null) && (lev2status != null)); - - try (FSDataInputStream inputStream = fs.open(path)) { - byte[] buffer = new byte[stringLen]; - // This read will not change the offset inside the file - int readBytes = inputStream.read(0, buffer, 0, buffer.length); - String out = new String(buffer, 0, buffer.length, UTF_8); - assertEquals(data, out); - assertEquals(readBytes, buffer.length); - assertEquals(0, inputStream.getPos()); - - // The following read will change the internal offset - readBytes = inputStream.read(buffer, 0, buffer.length); - assertEquals(data, out); - assertEquals(readBytes, buffer.length); - assertEquals(buffer.length, inputStream.getPos()); - } - assertEquals(statistics.getLong( - StorageStatistics.CommonStatisticNames.OP_OPEN).longValue(), 1); - assertEquals(statistics.getLong("objects_read").longValue(), 1); - } - - @Test - public void testReplication() throws IOException { - assumeFalse(FILE_SYSTEM_OPTIMIZED.equals(getBucketLayout())); - - int stringLen = 20; - String data = RandomStringUtils.randomAlphanumeric(stringLen); - String filePath = RandomStringUtils.randomAlphanumeric(5); - - Path pathIllegal = createPath("/" + filePath + "illegal"); - try (FSDataOutputStream streamIllegal = fs.create(pathIllegal, (short)2)) { - streamIllegal.writeBytes(data); - } - assertEquals(3, fs.getFileStatus(pathIllegal).getReplication()); - - Path pathLegal = createPath("/" + filePath + "legal"); - try (FSDataOutputStream streamLegal = fs.create(pathLegal, (short)1)) { - streamLegal.writeBytes(data); - } - assertEquals(1, fs.getFileStatus(pathLegal).getReplication()); - } - - private void verifyOwnerGroup(FileStatus fileStatus) { - String owner = getCurrentUser(); - assertEquals(owner, fileStatus.getOwner()); - assertEquals(owner, fileStatus.getGroup()); - } - - - @Test - public void testDirectory() throws IOException { - assumeFalse(FILE_SYSTEM_OPTIMIZED.equals(getBucketLayout())); - - String leafName = RandomStringUtils.randomAlphanumeric(5); - OMMetadataManager metadataManager = cluster.getOzoneManager() - .getMetadataManager(); - - String lev1dir = "abc"; - Path lev1path = createPath("/" + lev1dir); - String lev1key = metadataManager.getOzoneDirKey(volumeName, bucketName, - o3fs.pathToKey(lev1path)); - String lev2dir = "def"; - Path lev2path = createPath("/" + lev1dir + "/" + lev2dir); - String lev2key = metadataManager.getOzoneDirKey(volumeName, bucketName, - o3fs.pathToKey(lev2path)); - - FileStatus rootChild; - FileStatus rootstatus; - FileStatus leafstatus; - - Path leaf = createPath("/" + lev1dir + "/" + lev2dir + "/" + leafName); - String leafKey = metadataManager.getOzoneDirKey(volumeName, bucketName, - o3fs.pathToKey(leaf)); - - // verify prefix directories and the leaf, do not already exist - assertTrue( - metadataManager.getKeyTable(getBucketLayout()).get(lev1key) == null); - assertTrue( - metadataManager.getKeyTable(getBucketLayout()).get(lev2key) == null); - assertTrue( - metadataManager.getKeyTable(getBucketLayout()).get(leafKey) == null); - - assertTrue("Makedirs returned with false for the path " + leaf, - fs.mkdirs(leaf)); - - // verify the leaf directory got created. - leafstatus = getDirectoryStat(leaf); - assertTrue(leafstatus != null); - - FileStatus lev1status; - FileStatus lev2status; - - // verify prefix directories got created when creating the leaf directory. - assertTrue(metadataManager - .getKeyTable(getBucketLayout()) - .get(lev1key) - .getKeyName().equals("abc/")); - assertTrue(metadataManager - .getKeyTable(getBucketLayout()) - .get(lev2key) - .getKeyName().equals("abc/def/")); - lev1status = getDirectoryStat(lev1path); - lev2status = getDirectoryStat(lev2path); - assertTrue((lev1status != null) && (lev2status != null)); - rootChild = lev1status; - - // check the root directory - rootstatus = getDirectoryStat(createPath("/")); - assertTrue(rootstatus != null); - - // root directory listing should contain the lev1 prefix directory - FileStatus[] statusList = fs.listStatus(createPath("/")); - assertEquals(1, statusList.length); - assertEquals(rootChild, statusList[0]); - } - - @Test - public void testListStatus() throws IOException { - List paths = new ArrayList<>(); - String dirPath = RandomStringUtils.randomAlphanumeric(5); - Path path = createPath("/" + dirPath); - paths.add(path); - - long mkdirs = statistics.getLong( - StorageStatistics.CommonStatisticNames.OP_MKDIRS); - assertTrue("Makedirs returned with false for the path " + path, - fs.mkdirs(path)); - assertCounter(++mkdirs, StorageStatistics.CommonStatisticNames.OP_MKDIRS); - - long listObjects = statistics.getLong(Statistic.OBJECTS_LIST.getSymbol()); - long omListStatus = omMetrics.getNumListStatus(); - FileStatus[] statusList = fs.listStatus(createPath("/")); - assertEquals(1, statusList.length); - assertCounter(++listObjects, Statistic.OBJECTS_LIST.getSymbol()); - assertEquals(++omListStatus, omMetrics.getNumListStatus()); - assertEquals(fs.getFileStatus(path), statusList[0]); - - dirPath = RandomStringUtils.randomAlphanumeric(5); - path = createPath("/" + dirPath); - paths.add(path); - assertTrue("Makedirs returned with false for the path " + path, - fs.mkdirs(path)); - assertCounter(++mkdirs, StorageStatistics.CommonStatisticNames.OP_MKDIRS); - - statusList = fs.listStatus(createPath("/")); - assertEquals(2, statusList.length); - assertCounter(++listObjects, Statistic.OBJECTS_LIST.getSymbol()); - assertEquals(++omListStatus, omMetrics.getNumListStatus()); - for (Path p : paths) { - assertTrue(Arrays.asList(statusList).contains(fs.getFileStatus(p))); - } - } - - @Test - public void testOzoneManagerFileSystemInterface() throws IOException { - String dirPath = RandomStringUtils.randomAlphanumeric(5); - - Path path = createPath("/" + dirPath); - assertTrue("Makedirs returned with false for the path " + path, - fs.mkdirs(path)); - - long numFileStatus = - cluster.getOzoneManager().getMetrics().getNumGetFileStatus(); - FileStatus status = fs.getFileStatus(path); - - assertEquals(numFileStatus + 1, - cluster.getOzoneManager().getMetrics().getNumGetFileStatus()); - assertTrue(status.isDirectory()); - assertEquals(FsPermission.getDirDefault(), status.getPermission()); - verifyOwnerGroup(status); - - long currentTime = System.currentTimeMillis(); - OmKeyArgs keyArgs = new OmKeyArgs.Builder() - .setVolumeName(volumeName) - .setBucketName(bucketName) - .setKeyName(o3fs.pathToKey(path)) - .build(); - OzoneFileStatus omStatus = - cluster.getOzoneManager().getFileStatus(keyArgs); - //Another get file status here, incremented the counter. - assertEquals(numFileStatus + 2, - cluster.getOzoneManager().getMetrics().getNumGetFileStatus()); - - assertTrue("The created path is not directory.", omStatus.isDirectory()); - - // For directories, the time returned is the current time when the dir key - // doesn't actually exist on server; if it exists, it will be a fixed value. - // In this case, the dir key exists. - assertEquals(0, omStatus.getKeyInfo().getDataSize()); - assertTrue(omStatus.getKeyInfo().getModificationTime() <= currentTime); - assertEquals(new Path(omStatus.getPath()).getName(), - o3fs.pathToKey(path)); - } - - @Test - public void testOzoneManagerLocatedFileStatus() throws IOException { - String data = RandomStringUtils.randomAlphanumeric(20); - String filePath = RandomStringUtils.randomAlphanumeric(5); - Path path = createPath("/" + filePath); - try (FSDataOutputStream stream = fs.create(path)) { - stream.writeBytes(data); - } - FileStatus status = fs.getFileStatus(path); - assertTrue(status instanceof LocatedFileStatus); - LocatedFileStatus locatedFileStatus = (LocatedFileStatus) status; - assertTrue(locatedFileStatus.getBlockLocations().length >= 1); - - for (BlockLocation blockLocation : locatedFileStatus.getBlockLocations()) { - assertTrue(blockLocation.getNames().length >= 1); - assertTrue(blockLocation.getHosts().length >= 1); - } - } - - @Test - public void testOzoneManagerLocatedFileStatusBlockOffsetsWithMultiBlockFile() - throws Exception { - // naive assumption: MiniOzoneCluster will not have larger than ~1GB - // block size when running this test. - int blockSize = (int) fs.getConf().getStorageSize( - OzoneConfigKeys.OZONE_SCM_BLOCK_SIZE, - OzoneConfigKeys.OZONE_SCM_BLOCK_SIZE_DEFAULT, - StorageUnit.BYTES - ); - String data = RandomStringUtils.randomAlphanumeric(2 * blockSize + 837); - String filePath = RandomStringUtils.randomAlphanumeric(5); - Path path = createPath("/" + filePath); - try (FSDataOutputStream stream = fs.create(path)) { - stream.writeBytes(data); - } - FileStatus status = fs.getFileStatus(path); - assertTrue(status instanceof LocatedFileStatus); - LocatedFileStatus locatedFileStatus = (LocatedFileStatus) status; - BlockLocation[] blockLocations = locatedFileStatus.getBlockLocations(); - - assertEquals(0, blockLocations[0].getOffset()); - assertEquals(blockSize, blockLocations[1].getOffset()); - assertEquals(2 * blockSize, blockLocations[2].getOffset()); - assertEquals(blockSize, blockLocations[0].getLength()); - assertEquals(blockSize, blockLocations[1].getLength()); - assertEquals(837, blockLocations[2].getLength()); - } - - @Test - public void testPathToKey() throws Exception { - assumeFalse(FILE_SYSTEM_OPTIMIZED.equals(getBucketLayout())); - - assertEquals("a/b/1", o3fs.pathToKey(new Path("/a/b/1"))); - - assertEquals("user/" + getCurrentUser() + "/key1/key2", - o3fs.pathToKey(new Path("key1/key2"))); - - assertEquals("key1/key2", - o3fs.pathToKey(new Path("o3fs://test1/key1/key2"))); - } - - - /** - * Verify that FS throws exception when trying to access bucket with - * incompatible layout. - * @throws IOException - */ - @Test - public void testFileSystemWithObjectStoreLayout() throws IOException { - String obsVolume = UUID.randomUUID().toString(); - - try (OzoneClient client = cluster.newClient()) { - ObjectStore store = client.getObjectStore(); - - // Create volume and bucket - store.createVolume(obsVolume); - OzoneVolume volume = store.getVolume(obsVolume); - String obsBucket = UUID.randomUUID().toString(); - // create bucket with OBJECT_STORE bucket layout (incompatible with fs) - volume.createBucket(obsBucket, - BucketArgs.newBuilder().setBucketLayout(BucketLayout.OBJECT_STORE) - .build()); - - String obsRootPath = String.format("%s://%s.%s/", - OzoneConsts.OZONE_URI_SCHEME, obsBucket, obsVolume); - - OzoneConfiguration config = (OzoneConfiguration) fs.getConf(); - config.set(FS_DEFAULT_NAME_KEY, obsRootPath); - - try { - fs = FileSystem.get(fs.getConf()); - fail("Should throw Exception due incompatible bucket layout"); - } catch (IllegalArgumentException iae) { - // Expected exception - assertTrue(iae.getMessage().contains( - "OBJECT_STORE, which does not support file system semantics")); - } - } - } - - private String getCurrentUser() { - try { - return UserGroupInformation.getCurrentUser().getShortUserName(); - } catch (IOException e) { - return OZONE_DEFAULT_USER; - } - } - - private Path createPath(String relativePath) { - if (useAbsolutePath) { - return new Path( - rootPath + (relativePath.startsWith("/") ? "" : "/") + relativePath); - } else { - return new Path(relativePath); - } - } - - /** - * verify that a directory exists and is initialized correctly. - * @param path of the directory - * @return null indicates FILE_NOT_FOUND, else the FileStatus - * @throws IOException - */ - private FileStatus getDirectoryStat(Path path) throws IOException { - - FileStatus status = null; - - try { - status = fs.getFileStatus(path); - } catch (FileNotFoundException e) { - return null; - } - assertTrue("The created path is not directory.", status.isDirectory()); - - assertEquals(FsPermission.getDirDefault(), status.getPermission()); - verifyOwnerGroup(status); - - assertEquals(0, status.getLen()); - - return status; - } - - private void assertCounter(long value, String key) { - assertEquals(value, statistics.getLong(key).longValue()); - } - - public BucketLayout getBucketLayout() { - return BucketLayout.DEFAULT; - } -} diff --git a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestSafeMode.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestSafeMode.java index d97bc5b33754..f285abfaf75b 100644 --- a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestSafeMode.java +++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestSafeMode.java @@ -20,7 +20,9 @@ import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.SafeMode; import org.apache.hadoop.fs.SafeModeAction; +import org.apache.hadoop.hdds.client.RatisReplicationConfig; import org.apache.hadoop.hdds.conf.OzoneConfiguration; +import org.apache.hadoop.hdds.scm.container.common.helpers.ExcludeList; import org.apache.hadoop.hdds.utils.IOUtils; import org.apache.hadoop.ozone.MiniOzoneCluster; import org.apache.hadoop.ozone.MiniOzoneClusterProvider; @@ -30,18 +32,24 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; import java.io.IOException; import java.net.URI; import java.util.function.Function; +import static org.apache.hadoop.hdds.protocol.proto.HddsProtos.ReplicationFactor.THREE; +import static org.apache.hadoop.ozone.OzoneConsts.MB; +import static org.apache.hadoop.ozone.OzoneConsts.OZONE; import static org.apache.hadoop.ozone.OzoneConsts.OZONE_OFS_URI_SCHEME; import static org.apache.hadoop.ozone.OzoneConsts.OZONE_URI_SCHEME; import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_ADDRESS_KEY; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +@Timeout(60) class TestSafeMode { private static final String VOLUME = "vol"; @@ -106,6 +114,13 @@ private void testSafeMode(Function fsRoot) // force exit safe mode and verify that it's out of safe mode. subject.setSafeMode(SafeModeAction.FORCE_EXIT); assertFalse(subject.setSafeMode(SafeModeAction.GET)); + + // datanodes are still stopped + RatisReplicationConfig replication = + RatisReplicationConfig.getInstance(THREE); + assertThrows(IOException.class, () -> cluster.getStorageContainerManager() + .getWritableContainerFactory() + .getContainer(MB, replication, OZONE, new ExcludeList())); } finally { IOUtils.closeQuietly(fs); } diff --git a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/scm/storage/TestContainerCommandsEC.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/scm/storage/TestContainerCommandsEC.java index c5f123935383..a35e2198c110 100644 --- a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/scm/storage/TestContainerCommandsEC.java +++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/scm/storage/TestContainerCommandsEC.java @@ -452,10 +452,10 @@ public void testCreateRecoveryContainer() throws Exception { .generateToken(ANY_USER, container.containerID()); scm.getContainerManager().getContainerStateManager() .addContainer(container.getProtobuf()); - + int replicaIndex = 4; XceiverClientSpi dnClient = xceiverClientManager.acquireClient( createSingleNodePipeline(newPipeline, newPipeline.getNodes().get(0), - 2)); + replicaIndex)); try { // To create the actual situation, container would have been in closed // state at SCM. @@ -470,7 +470,7 @@ public void testCreateRecoveryContainer() throws Exception { String encodedToken = cToken.encodeToUrlString(); ContainerProtocolCalls.createRecoveringContainer(dnClient, container.containerID().getProtobuf().getId(), - encodedToken, 4); + encodedToken, replicaIndex); BlockID blockID = ContainerTestHelper .getTestBlockID(container.containerID().getProtobuf().getId()); @@ -511,7 +511,8 @@ public void testCreateRecoveryContainer() throws Exception { readContainerResponseProto.getContainerData().getState()); ContainerProtos.ReadChunkResponseProto readChunkResponseProto = ContainerProtocolCalls.readChunk(dnClient, - writeChunkRequest.getWriteChunk().getChunkData(), blockID, null, + writeChunkRequest.getWriteChunk().getChunkData(), + blockID.getDatanodeBlockIDProtobufBuilder().setReplicaIndex(replicaIndex).build(), null, blockToken); ByteBuffer[] readOnlyByteBuffersArray = BufferUtils .getReadOnlyByteBuffersArray( diff --git a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestBlockTokens.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestBlockTokens.java index f410c50c4662..752c011f4688 100644 --- a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestBlockTokens.java +++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestBlockTokens.java @@ -22,6 +22,7 @@ import org.apache.hadoop.hdds.annotation.InterfaceAudience; import org.apache.hadoop.hdds.conf.DefaultConfigManager; import org.apache.hadoop.hdds.conf.OzoneConfiguration; +import org.apache.hadoop.hdds.scm.OzoneClientConfig; import org.apache.hadoop.hdds.scm.ScmConfig; import org.apache.hadoop.hdds.scm.XceiverClientFactory; import org.apache.hadoop.hdds.scm.container.common.helpers.StorageContainerException; @@ -305,9 +306,11 @@ private void readData(OmKeyInfo keyInfo, Function retryFunc) throws IOException { XceiverClientFactory xceiverClientManager = ((RpcClient) client.getProxy()).getXceiverClientManager(); + OzoneClientConfig clientConfig = conf.getObject(OzoneClientConfig.class); + clientConfig.setChecksumVerify(false); try (InputStream is = KeyInputStream.getFromOmKeyInfo(keyInfo, - xceiverClientManager, - false, retryFunc, blockInputStreamFactory)) { + xceiverClientManager, retryFunc, blockInputStreamFactory, + clientConfig)) { byte[] buf = new byte[100]; int readBytes = is.read(buf, 0, 100); assertEquals(100, readBytes); diff --git a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestOzoneConfigurationFields.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestOzoneConfigurationFields.java index cb29d61e1a4c..1a437be8131b 100644 --- a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestOzoneConfigurationFields.java +++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestOzoneConfigurationFields.java @@ -141,7 +141,10 @@ private void addPropertiesNotInXml() { ScmConfigKeys.OZONE_SCM_PIPELINE_PLACEMENT_IMPL_KEY, ScmConfigKeys.OZONE_SCM_HA_PREFIX, S3GatewayConfigKeys.OZONE_S3G_FSO_DIRECTORY_CREATION_ENABLED, - HddsConfigKeys.HDDS_DATANODE_VOLUME_MIN_FREE_SPACE_PERCENT + HddsConfigKeys.HDDS_DATANODE_VOLUME_MIN_FREE_SPACE_PERCENT, + OzoneConfigKeys.HDDS_SCM_CLIENT_RPC_TIME_OUT, + OzoneConfigKeys.HDDS_SCM_CLIENT_MAX_RETRY_TIMEOUT, + OzoneConfigKeys.HDDS_SCM_CLIENT_FAILOVER_MAX_RETRY )); } } diff --git a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/TestOzoneRpcClientWithRatis.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/TestOzoneRpcClientWithRatis.java index c3bbd793dc1c..3a8d117bc6d5 100644 --- a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/TestOzoneRpcClientWithRatis.java +++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/TestOzoneRpcClientWithRatis.java @@ -28,6 +28,7 @@ import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.HashMap; +import java.util.Locale; import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ThreadLocalRandom; @@ -65,6 +66,8 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import static java.nio.charset.StandardCharsets.UTF_8; import static org.apache.hadoop.hdds.client.ReplicationFactor.ONE; @@ -183,12 +186,13 @@ public void testGetKeyAndFileWithNetworkTopology() throws IOException { } } - @Test - public void testMultiPartUploadWithStream() + @ParameterizedTest + @MethodSource("replicationConfigs") + void testMultiPartUploadWithStream(ReplicationConfig replicationConfig) throws IOException, NoSuchAlgorithmException { String volumeName = UUID.randomUUID().toString(); - String bucketName = UUID.randomUUID().toString(); - String keyName = UUID.randomUUID().toString(); + String bucketName = replicationConfig.getReplicationType().name().toLowerCase(Locale.ROOT) + "-bucket"; + String keyName = replicationConfig.getReplication(); byte[] sampleData = new byte[1024 * 8]; @@ -199,11 +203,6 @@ public void testMultiPartUploadWithStream() volume.createBucket(bucketName); OzoneBucket bucket = volume.getBucket(bucketName); - ReplicationConfig replicationConfig = - ReplicationConfig.fromTypeAndFactor( - ReplicationType.RATIS, - THREE); - OmMultipartInfo multipartInfo = bucket.initiateMultipartUpload(keyName, replicationConfig); @@ -226,7 +225,7 @@ public void testMultiPartUploadWithStream() OzoneMultipartUploadPartListParts parts = bucket.listParts(keyName, uploadID, 0, 1); - Assert.assertEquals(parts.getPartInfoList().size(), 1); + Assert.assertEquals(1, parts.getPartInfoList().size()); OzoneMultipartUploadPartListParts.PartInfo partInfo = parts.getPartInfoList().get(0); diff --git a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/container/TestContainerReplication.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/container/TestContainerReplication.java index 28d19d0be87b..0a6da3d49ec5 100644 --- a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/container/TestContainerReplication.java +++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/container/TestContainerReplication.java @@ -21,7 +21,9 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Arrays.asList; import static java.util.Collections.emptyMap; +import static org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.ContainerType.KeyValueContainer; import static org.apache.hadoop.hdds.protocol.proto.HddsProtos.ReplicationFactor.THREE; +import static org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_CONTAINER_PLACEMENT_EC_IMPL_KEY; import static org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_CONTAINER_PLACEMENT_IMPL_KEY; import static org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_DEADNODE_INTERVAL; import static org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_STALENODE_INTERVAL; @@ -30,36 +32,62 @@ import static org.apache.ozone.test.GenericTestUtils.setLogLevel; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.any; import java.io.IOException; import java.io.OutputStream; import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import com.google.common.base.Functions; +import com.google.common.collect.ImmutableMap; +import org.apache.hadoop.hdds.client.ECReplicationConfig; import org.apache.hadoop.hdds.client.RatisReplicationConfig; import org.apache.hadoop.hdds.conf.OzoneConfiguration; +import org.apache.hadoop.hdds.protocol.DatanodeDetails; +import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos; import org.apache.hadoop.hdds.scm.PlacementPolicy; +import org.apache.hadoop.hdds.scm.container.placement.algorithms.SCMContainerPlacementRackScatter; import org.apache.hadoop.hdds.scm.container.replication.ReplicationManager.ReplicationManagerConfiguration; import org.apache.hadoop.hdds.scm.container.placement.algorithms.SCMContainerPlacementCapacity; import org.apache.hadoop.hdds.scm.container.placement.algorithms.SCMContainerPlacementRackAware; import org.apache.hadoop.hdds.scm.container.placement.algorithms.SCMContainerPlacementRandom; +import org.apache.hadoop.hdds.scm.storage.ContainerProtocolCalls; +import org.apache.hadoop.ozone.HddsDatanodeService; import org.apache.hadoop.ozone.MiniOzoneCluster; import org.apache.hadoop.ozone.client.ObjectStore; import org.apache.hadoop.ozone.client.OzoneBucket; import org.apache.hadoop.ozone.client.OzoneClient; +import org.apache.hadoop.ozone.client.OzoneClientFactory; import org.apache.hadoop.ozone.client.OzoneVolume; +import org.apache.hadoop.ozone.client.io.OzoneInputStream; +import org.apache.hadoop.ozone.container.common.interfaces.Container; +import org.apache.hadoop.ozone.container.ozoneimpl.OzoneContainer; import org.apache.hadoop.ozone.om.helpers.OmKeyArgs; import org.apache.hadoop.ozone.om.helpers.OmKeyInfo; import org.apache.hadoop.ozone.om.helpers.OmKeyLocationInfo; import org.apache.hadoop.ozone.om.helpers.OmKeyLocationInfoGroup; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.MockedStatic; +import org.mockito.Mockito; import org.slf4j.event.Level; /** @@ -159,6 +187,22 @@ private void createTestData(OzoneClient client) throws IOException { } } + private byte[] createTestData(OzoneClient client, int size) throws IOException { + ObjectStore objectStore = client.getObjectStore(); + objectStore.createVolume(VOLUME); + OzoneVolume volume = objectStore.getVolume(VOLUME); + volume.createBucket(BUCKET); + OzoneBucket bucket = volume.getBucket(BUCKET); + try (OutputStream out = bucket.createKey(KEY, 0, new ECReplicationConfig("RS-3-2-1k"), + new HashMap<>())) { + byte[] b = new byte[size]; + Random random = new Random(); + random.nextBytes(b); + out.write(b); + return b; + } + } + private static List lookupKey(MiniOzoneCluster cluster) throws IOException { OmKeyArgs keyArgs = new OmKeyArgs.Builder() @@ -172,4 +216,155 @@ private static List lookupKey(MiniOzoneCluster cluster) return locations.getLocationList(); } + private static OmKeyLocationInfo lookupKeyFirstLocation(MiniOzoneCluster cluster) + throws IOException { + OmKeyArgs keyArgs = new OmKeyArgs.Builder() + .setVolumeName(VOLUME) + .setBucketName(BUCKET) + .setKeyName(KEY) + .build(); + OmKeyInfo keyInfo = cluster.getOzoneManager().lookupKey(keyArgs); + OmKeyLocationInfoGroup locations = keyInfo.getLatestVersionLocations(); + Assertions.assertNotNull(locations); + return locations.getLocationList().get(0); + } + + + public void assertState(MiniOzoneCluster cluster, Map expectedReplicaMap) + throws IOException { + OmKeyLocationInfo keyLocation = lookupKeyFirstLocation(cluster); + Map replicaMap = + keyLocation.getPipeline().getNodes().stream().collect(Collectors.toMap( + dn -> keyLocation.getPipeline().getReplicaIndex(dn), Functions.identity())); + Assertions.assertEquals(expectedReplicaMap, replicaMap); + } + + private OzoneInputStream createInputStream(OzoneClient client) throws IOException { + ObjectStore objectStore = client.getObjectStore(); + OzoneVolume volume = objectStore.getVolume(VOLUME); + OzoneBucket bucket = volume.getBucket(BUCKET); + return bucket.readKey(KEY); + } + + private void mockContainerProtocolCalls(final MockedStatic mockedContainerProtocolCalls, + final Map failedReadChunkCountMap) { + mockedContainerProtocolCalls.when(() -> ContainerProtocolCalls.readChunk(any(), any(), any(), anyList(), any())) + .thenAnswer(invocation -> { + int replicaIndex = ((ContainerProtos.DatanodeBlockID) invocation.getArgument(2)).getReplicaIndex(); + try { + return invocation.callRealMethod(); + } catch (Throwable e) { + failedReadChunkCountMap.compute(replicaIndex, + (replicaIdx, totalCount) -> totalCount == null ? 1 : (totalCount + 1)); + throw e; + } + }); + } + + + private static void deleteContainer(MiniOzoneCluster cluster, DatanodeDetails dn, long containerId) + throws IOException { + OzoneContainer container = cluster.getHddsDatanode(dn).getDatanodeStateMachine().getContainer(); + Container containerData = container.getContainerSet().getContainer(containerId); + if (containerData != null) { + container.getDispatcher().getHandler(KeyValueContainer).deleteContainer(containerData, true); + } + cluster.getHddsDatanode(dn).getDatanodeStateMachine().triggerHeartbeat(); + } + + + @Test + public void testECContainerReplication() throws Exception { + OzoneConfiguration conf = createConfiguration(false); + final Map failedReadChunkCountMap = new ConcurrentHashMap<>(); + // Overiding Config to support 1k Chunk size + conf.set("ozone.replication.allowed-configs", "(^((STANDALONE|RATIS)/(ONE|THREE))|(EC/(3-2|6-3|10-4)-" + + "(512|1024|2048|4096|1)k)$)"); + conf.set(OZONE_SCM_CONTAINER_PLACEMENT_EC_IMPL_KEY, SCMContainerPlacementRackScatter.class.getCanonicalName()); + try (MockedStatic mockedContainerProtocolCalls = + Mockito.mockStatic(ContainerProtocolCalls.class, Mockito.CALLS_REAL_METHODS);) { + mockContainerProtocolCalls(mockedContainerProtocolCalls, failedReadChunkCountMap); + // Creating Cluster with 5 Nodes + try (MiniOzoneCluster cluster = MiniOzoneCluster.newBuilder(conf).setNumDatanodes(5).build()) { + cluster.waitForClusterToBeReady(); + try (OzoneClient client = OzoneClientFactory.getRpcClient(conf)) { + Set allNodes = + cluster.getHddsDatanodes().stream().map(HddsDatanodeService::getDatanodeDetails).collect( + Collectors.toSet()); + List initialNodesWithData = new ArrayList<>(); + // Keeping 5 DNs and stopping the 6th Node here it is kept in the var extraNodes + for (DatanodeDetails dn : allNodes) { + if (initialNodesWithData.size() < 5) { + initialNodesWithData.add(dn); + } else { + cluster.shutdownHddsDatanode(dn); + } + } + + // Creating 2 stripes with Chunk Size 1k + int size = 6 * 1024; + byte[] originalData = createTestData(client, size); + + // Getting latest location of the key + final OmKeyLocationInfo keyLocation = lookupKeyFirstLocation(cluster); + long containerID = keyLocation.getContainerID(); + waitForContainerClose(cluster, containerID); + + // Forming Replica Index Map + Map replicaIndexMap = + initialNodesWithData.stream().map(dn -> new Object[]{dn, keyLocation.getPipeline().getReplicaIndex(dn)}) + .collect( + Collectors.toMap(x -> (Integer) x[1], x -> (DatanodeDetails) x[0])); + + //Reading through file and comparing with input data. + byte[] readData = new byte[size]; + try (OzoneInputStream inputStream = createInputStream(client)) { + inputStream.read(readData); + Assertions.assertArrayEquals(readData, originalData); + } + Assertions.assertEquals(0, failedReadChunkCountMap.size()); + //Opening a new stream before we make changes to the blocks. + try (OzoneInputStream inputStream = createInputStream(client)) { + int firstReadLen = 1024 * 3; + Arrays.fill(readData, (byte) 0); + //Reading first stripe. + inputStream.read(readData, 0, firstReadLen); + Assertions.assertEquals(0, failedReadChunkCountMap.size()); + //Checking the initial state as per the latest location. + assertState(cluster, ImmutableMap.of(1, replicaIndexMap.get(1), 2, replicaIndexMap.get(2), + 3, replicaIndexMap.get(3), 4, replicaIndexMap.get(4), 5, replicaIndexMap.get(5))); + + // Stopping replication manager + cluster.getStorageContainerManager().getReplicationManager().stop(); + // Deleting the container from DN1 & DN3 + deleteContainer(cluster, replicaIndexMap.get(1), containerID); + deleteContainer(cluster, replicaIndexMap.get(3), containerID); + // Waiting for replica count of container to come down to 3. + waitForReplicaCount(containerID, 3, cluster); + // Shutting down DN1 + cluster.shutdownHddsDatanode(replicaIndexMap.get(1)); + // Starting replication manager which should process under replication & write replica 1 to DN3. + cluster.getStorageContainerManager().getReplicationManager().start(); + waitForReplicaCount(containerID, 4, cluster); + // Asserting Replica 1 has been written to DN3. + assertState(cluster, ImmutableMap.of(1, replicaIndexMap.get(3), 2, replicaIndexMap.get(2), + 4, replicaIndexMap.get(4), 5, replicaIndexMap.get(5))); + // Starting DN1. + cluster.restartHddsDatanode(replicaIndexMap.get(1), false); + // Waiting for underreplication to get resolved. + waitForReplicaCount(containerID, 5, cluster); + // Asserting Replica 1 & Replica 3 has been swapped b/w DN1 & DN3. + assertState(cluster, ImmutableMap.of(1, replicaIndexMap.get(3), 2, replicaIndexMap.get(2), + 3, replicaIndexMap.get(1), 4, replicaIndexMap.get(4), 5, replicaIndexMap.get(5))); + // Reading the Stripe 2 from the pre initialized inputStream + inputStream.read(readData, firstReadLen, size - firstReadLen); + // Asserting there was a failure in the first read chunk. + Assertions.assertEquals(ImmutableMap.of(1, 1, 3, 1), failedReadChunkCountMap); + Assertions.assertArrayEquals(readData, originalData); + } + } + } + } + } + } diff --git a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/freon/TestOMSnapshotDAG.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/freon/TestOMSnapshotDAG.java index 88fb0107969c..114ba1a21bbf 100644 --- a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/freon/TestOMSnapshotDAG.java +++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/freon/TestOMSnapshotDAG.java @@ -29,7 +29,6 @@ import org.apache.hadoop.ozone.client.OzoneBucket; import org.apache.hadoop.ozone.client.OzoneClient; import org.apache.hadoop.ozone.client.OzoneVolume; -import org.apache.hadoop.ozone.om.IOmMetadataReader; import org.apache.hadoop.ozone.om.OMConfigKeys; import org.apache.hadoop.ozone.om.OMMetadataManager; import org.apache.hadoop.ozone.om.OmSnapshot; @@ -39,7 +38,6 @@ import org.apache.hadoop.ozone.om.helpers.OmVolumeArgs; import org.apache.hadoop.ozone.om.helpers.SnapshotInfo; import org.apache.hadoop.ozone.om.snapshot.ReferenceCounted; -import org.apache.hadoop.ozone.om.snapshot.SnapshotCache; import org.apache.ozone.rocksdiff.DifferSnapshotInfo; import org.apache.ozone.rocksdiff.RocksDBCheckpointDiffer; import org.apache.ozone.test.GenericTestUtils; @@ -213,20 +211,16 @@ public void testDAGReconstruction() OMMetadataManager omMetadataManager = ozoneManager.getMetadataManager(); RDBStore rdbStore = (RDBStore) omMetadataManager.getStore(); RocksDBCheckpointDiffer differ = rdbStore.getRocksDBCheckpointDiffer(); - ReferenceCounted - snapDB1 = ozoneManager.getOmSnapshotManager() - .getSnapshotCache().get( - SnapshotInfo.getTableKey(volumeName, bucketName, "snap1")); - ReferenceCounted - snapDB2 = ozoneManager.getOmSnapshotManager() - .getSnapshotCache().get( - SnapshotInfo.getTableKey(volumeName, bucketName, "snap2")); + ReferenceCounted snapDB1 = ozoneManager.getOmSnapshotManager() + .getActiveSnapshot(volumeName, bucketName, "snap1"); + ReferenceCounted snapDB2 = ozoneManager.getOmSnapshotManager() + .getActiveSnapshot(volumeName, bucketName, "snap2"); DifferSnapshotInfo snap1 = getDifferSnapshotInfo(omMetadataManager, volumeName, bucketName, "snap1", - ((RDBStore)((OmSnapshot)snapDB1.get()) + ((RDBStore) snapDB1.get() .getMetadataManager().getStore()).getDb().getManagedRocksDb()); DifferSnapshotInfo snap2 = getDifferSnapshotInfo(omMetadataManager, - volumeName, bucketName, "snap2", ((RDBStore)((OmSnapshot)snapDB2.get()) + volumeName, bucketName, "snap2", ((RDBStore) snapDB2.get() .getMetadataManager().getStore()).getDb().getManagedRocksDb()); // RocksDB does checkpointing in a separate thread, wait for it @@ -245,13 +239,11 @@ public void testDAGReconstruction() resp = store.createSnapshot(volumeName, bucketName, "snap3"); LOG.debug("Snapshot created: {}", resp); - ReferenceCounted - snapDB3 = ozoneManager.getOmSnapshotManager() - .getSnapshotCache().get( - SnapshotInfo.getTableKey(volumeName, bucketName, "snap3")); + ReferenceCounted snapDB3 = ozoneManager.getOmSnapshotManager() + .getActiveSnapshot(volumeName, bucketName, "snap3"); DifferSnapshotInfo snap3 = getDifferSnapshotInfo(omMetadataManager, volumeName, bucketName, "snap3", - ((RDBStore)((OmSnapshot)snapDB3.get()) + ((RDBStore) snapDB3.get() .getMetadataManager().getStore()).getDb().getManagedRocksDb()); final File checkpointSnap3 = new File(snap3.getDbPath()); GenericTestUtils.waitFor(checkpointSnap3::exists, 2000, 20000); @@ -272,24 +264,21 @@ public void testDAGReconstruction() ozoneManager = cluster.getOzoneManager(); omMetadataManager = ozoneManager.getMetadataManager(); snapDB1 = ozoneManager.getOmSnapshotManager() - .getSnapshotCache().get( - SnapshotInfo.getTableKey(volumeName, bucketName, "snap1")); + .getActiveSnapshot(volumeName, bucketName, "snap1"); snapDB2 = ozoneManager.getOmSnapshotManager() - .getSnapshotCache().get( - SnapshotInfo.getTableKey(volumeName, bucketName, "snap2")); + .getActiveSnapshot(volumeName, bucketName, "snap2"); snap1 = getDifferSnapshotInfo(omMetadataManager, volumeName, bucketName, "snap1", - ((RDBStore)((OmSnapshot)snapDB1.get()) + ((RDBStore) snapDB1.get() .getMetadataManager().getStore()).getDb().getManagedRocksDb()); snap2 = getDifferSnapshotInfo(omMetadataManager, - volumeName, bucketName, "snap2", ((RDBStore)((OmSnapshot)snapDB2.get()) + volumeName, bucketName, "snap2", ((RDBStore) snapDB2.get() .getMetadataManager().getStore()).getDb().getManagedRocksDb()); snapDB3 = ozoneManager.getOmSnapshotManager() - .getSnapshotCache().get( - SnapshotInfo.getTableKey(volumeName, bucketName, "snap3")); + .getActiveSnapshot(volumeName, bucketName, "snap3"); snap3 = getDifferSnapshotInfo(omMetadataManager, volumeName, bucketName, "snap3", - ((RDBStore)((OmSnapshot)snapDB3.get()) + ((RDBStore) snapDB3.get() .getMetadataManager().getStore()).getDb().getManagedRocksDb()); List sstDiffList21Run2 = differ.getSSTDiffList(snap2, snap1); Assertions.assertEquals(sstDiffList21, sstDiffList21Run2); diff --git a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestAddRemoveOzoneManager.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestAddRemoveOzoneManager.java index f2d6a0d80d2c..67083d63bb2c 100644 --- a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestAddRemoveOzoneManager.java +++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestAddRemoveOzoneManager.java @@ -46,7 +46,6 @@ import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.util.StringUtils; import org.apache.ozone.test.GenericTestUtils; -import org.apache.ozone.test.tag.Flaky; import org.apache.ratis.grpc.server.GrpcLogAppender; import org.apache.ratis.server.leader.FollowerInfo; import org.junit.Assert; @@ -174,7 +173,6 @@ private List testBootstrapOMs(int numNewOMs) throws Exception { * OM. */ @Test - @Flaky("HDDS-7880") public void testBootstrap() throws Exception { setupCluster(1); OzoneManager oldOM = cluster.getOzoneManager(); diff --git a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestSnapshotBackgroundServices.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestSnapshotBackgroundServices.java index 682d02cd2861..8bd78460c1bd 100644 --- a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestSnapshotBackgroundServices.java +++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestSnapshotBackgroundServices.java @@ -41,7 +41,6 @@ import org.apache.hadoop.ozone.om.helpers.SnapshotInfo; import org.apache.hadoop.ozone.om.ratis.OzoneManagerRatisServerConfig; import org.apache.hadoop.ozone.om.snapshot.ReferenceCounted; -import org.apache.hadoop.ozone.om.snapshot.SnapshotCache; import org.apache.hadoop.ozone.snapshot.SnapshotDiffReportOzone; import org.apache.hadoop.ozone.snapshot.SnapshotDiffResponse; import org.apache.ozone.compaction.log.CompactionLogEntry; @@ -78,7 +77,6 @@ import static org.apache.hadoop.ozone.OzoneConsts.OM_KEY_PREFIX; import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_SNAPSHOT_SST_FILTERING_SERVICE_INTERVAL; import static org.apache.hadoop.ozone.om.OmSnapshotManager.getSnapshotPath; -import static org.apache.hadoop.ozone.om.OmSnapshotManager.getSnapshotPrefix; import static org.apache.hadoop.ozone.om.TestOzoneManagerHAWithStoppedNodes.createKey; import static org.apache.hadoop.ozone.snapshot.SnapshotDiffResponse.JobStatus.DONE; @@ -262,12 +260,11 @@ public void testSnapshotAndKeyDeletionBackgroundServices() // get snapshot c OmSnapshot snapC; - try (ReferenceCounted rcC = newLeaderOM + try (ReferenceCounted rcC = newLeaderOM .getOmSnapshotManager() - .checkForSnapshot(volumeName, bucketName, - getSnapshotPrefix(snapshotInfoC.getName()), true)) { + .getSnapshot(volumeName, bucketName, snapshotInfoC.getName())) { Assertions.assertNotNull(rcC); - snapC = (OmSnapshot) rcC.get(); + snapC = rcC.get(); } // assert that key a is in snapshot c's deleted table @@ -287,12 +284,11 @@ public void testSnapshotAndKeyDeletionBackgroundServices() // get snapshot d OmSnapshot snapD; - try (ReferenceCounted rcD = newLeaderOM + try (ReferenceCounted rcD = newLeaderOM .getOmSnapshotManager() - .checkForSnapshot(volumeName, bucketName, - getSnapshotPrefix(snapshotInfoD.getName()), true)) { + .getSnapshot(volumeName, bucketName, snapshotInfoD.getName())) { Assertions.assertNotNull(rcD); - snapD = (OmSnapshot) rcD.get(); + snapD = rcD.get(); } // wait until key a appears in deleted table of snapshot d diff --git a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestSnapshotDeletingService.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestSnapshotDeletingService.java index 843a955ecde0..785ae8c25df5 100644 --- a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestSnapshotDeletingService.java +++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestSnapshotDeletingService.java @@ -39,7 +39,6 @@ import org.apache.hadoop.ozone.om.helpers.SnapshotInfo; import org.apache.hadoop.ozone.om.service.SnapshotDeletingService; import org.apache.hadoop.ozone.om.snapshot.ReferenceCounted; -import org.apache.hadoop.ozone.om.snapshot.SnapshotCache; import org.apache.ozone.test.GenericTestUtils; import org.apache.ozone.test.tag.Flaky; import org.junit.jupiter.api.AfterEach; @@ -59,7 +58,6 @@ import static org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_CHUNK_SIZE_KEY; import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_ACL_ENABLED; import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_BLOCK_DELETING_SERVICE_INTERVAL; -import static org.apache.hadoop.ozone.om.OmSnapshotManager.getSnapshotPrefix; import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_SNAPSHOT_DELETING_SERVICE_INTERVAL; import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_SNAPSHOT_DELETING_SERVICE_TIMEOUT; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -134,9 +132,8 @@ public void testSnapshotSplitAndMove() throws Exception { GenericTestUtils.waitFor(() -> snapshotDeletingService .getSuccessfulRunCount() >= 1, 1000, 10000); - OmSnapshot bucket1snap3 = (OmSnapshot) om.getOmSnapshotManager() - .checkForSnapshot(VOLUME_NAME, BUCKET_NAME_ONE, - getSnapshotPrefix("bucket1snap3"), true).get(); + OmSnapshot bucket1snap3 = om.getOmSnapshotManager() + .getSnapshot(VOLUME_NAME, BUCKET_NAME_ONE, "bucket1snap3").get(); // Check bucket1key1 added to next non deleted snapshot db. List> omKeyInfos = @@ -192,8 +189,7 @@ public void testMultipleSnapshotKeyReclaim() throws Exception { // verify the cache of purged snapshot // /vol1/bucket2/bucket2snap1 has been cleaned up from cache map - SnapshotCache snapshotCache = om.getOmSnapshotManager().getSnapshotCache(); - assertEquals(2, snapshotCache.size()); + assertEquals(2, om.getOmSnapshotManager().getSnapshotCacheSize()); } @SuppressWarnings("checkstyle:MethodLength") @@ -362,9 +358,8 @@ public void testSnapshotWithFSO() throws Exception { assertTableRowCount(om.getMetadataManager().getSnapshotInfoTable(), 2); verifySnapshotChain(deletedSnap, "/vol1/bucket2/snap3"); - OmSnapshot snap3 = (OmSnapshot) om.getOmSnapshotManager() - .checkForSnapshot(VOLUME_NAME, BUCKET_NAME_TWO, - getSnapshotPrefix("snap3"), true).get(); + OmSnapshot snap3 = om.getOmSnapshotManager() + .getSnapshot(VOLUME_NAME, BUCKET_NAME_TWO, "snap3").get(); Table snapDeletedDirTable = snap3.getMetadataManager().getDeletedDirTable(); @@ -391,10 +386,10 @@ public void testSnapshotWithFSO() throws Exception { assertTableRowCount(renamedTable, 4); assertTableRowCount(deletedDirTable, 3); - ReferenceCounted rcSnap1 = - om.getOmSnapshotManager().checkForSnapshot( - VOLUME_NAME, BUCKET_NAME_TWO, getSnapshotPrefix("snap1"), true); - OmSnapshot snap1 = (OmSnapshot) rcSnap1.get(); + ReferenceCounted rcSnap1 = + om.getOmSnapshotManager().getSnapshot( + VOLUME_NAME, BUCKET_NAME_TWO, "snap1"); + OmSnapshot snap1 = rcSnap1.get(); Table snap1KeyTable = snap1.getMetadataManager().getFileTable(); try (TableIterator keyMetadataList = data.getKeys(); + + assertEquals(1, data.getTotalCount()); + assertEquals(1, keyMetadataList.size()); + + // Assert the file name and the complete path. + KeyMetadata keyMetadata = keyMetadataList.iterator().next(); + assertEquals("file1", keyMetadata.getKey()); + assertEquals("testvol/fsobucket/dir1/dir2/dir3/file1", keyMetadata.getCompletePath()); + + testContainerID = 2L; + response = getContainerEndpointResponse(testContainerID); + data = (KeysResponse) response.getEntity(); + keyMetadataList = data.getKeys(); + assertEquals(1, data.getTotalCount()); + assertEquals(1, keyMetadataList.size()); + + // Assert the file name and the complete path. + keyMetadata = keyMetadataList.iterator().next(); + assertEquals("file1", keyMetadata.getKey()); + assertEquals("testvol/fsobucket/file1", keyMetadata.getCompletePath()); + } + + @Test + public void testContainerEndpointForOBSBucket() throws Exception { + String volumeName = "testvol2"; + String obsBucketName = "obsbucket"; + String obsSingleFileKey = "file1"; + + // Setup volume and OBS bucket + store.createVolume(volumeName); + OzoneVolume volume = store.getVolume(volumeName); + volume.createBucket(obsBucketName, + BucketArgs.newBuilder().setBucketLayout(BucketLayout.OBJECT_STORE) + .build()); + + // Write a single file to the OBS bucket + writeTestData(volumeName, obsBucketName, obsSingleFileKey, "Hello OBS!"); + + OzoneManagerServiceProviderImpl impl = + (OzoneManagerServiceProviderImpl) cluster.getReconServer() + .getOzoneManagerServiceProvider(); + impl.syncDataFromOM(); + + // Search for the bucket from the bucket table and verify its OBS + OmBucketInfo bucketInfo = cluster.getOzoneManager().getBucketInfo(volumeName, obsBucketName); + assertNotNull(bucketInfo); + assertEquals(BucketLayout.OBJECT_STORE, bucketInfo.getBucketLayout()); + + // Initialize the ContainerEndpoint + long containerId = 1L; + Response response = getContainerEndpointResponse(containerId); + + assertNotNull(response, "Response should not be null."); + assertEquals(Response.Status.OK.getStatusCode(), response.getStatus(), + "Expected HTTP 200 OK response."); + KeysResponse data = (KeysResponse) response.getEntity(); + Collection keyMetadataList = data.getKeys(); + + assertEquals(1, data.getTotalCount()); + assertEquals(1, keyMetadataList.size()); + + KeyMetadata keyMetadata = keyMetadataList.iterator().next(); + assertEquals("file1", keyMetadata.getKey()); + assertEquals("testvol2/obsbucket/file1", keyMetadata.getCompletePath()); + } + + private Response getContainerEndpointResponse(long containerId) { + OzoneStorageContainerManager reconSCM = + cluster.getReconServer().getReconStorageContainerManager(); + ReconContainerManager reconContainerManager = + (ReconContainerManager) reconSCM.getContainerManager(); + ContainerHealthSchemaManager containerHealthSchemaManager = + reconContainerManager.getContainerSchemaManager(); + ReconOMMetadataManager omMetadataManagerInstance = + (ReconOMMetadataManager) + cluster.getReconServer().getOzoneManagerServiceProvider() + .getOMMetadataManagerInstance(); + ContainerEndpoint containerEndpoint = + new ContainerEndpoint(reconSCM, containerHealthSchemaManager, + cluster.getReconServer().getReconNamespaceSummaryManager(), + cluster.getReconServer().getReconContainerMetadataManager(), + omMetadataManagerInstance); + return containerEndpoint.getKeysForContainer(containerId, 10, ""); + } + + private void writeTestData(String volumeName, String bucketName, + String keyPath, String data) throws Exception { + try (OzoneOutputStream out = client.getObjectStore().getVolume(volumeName) + .getBucket(bucketName) + .createKey(keyPath, data.length())) { + out.write(data.getBytes(StandardCharsets.UTF_8)); + } + } + +} diff --git a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/scm/TestXceiverClientGrpc.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/scm/TestXceiverClientGrpc.java index ae156be8a482..5dd6320c1d4d 100644 --- a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/scm/TestXceiverClientGrpc.java +++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/scm/TestXceiverClientGrpc.java @@ -202,11 +202,11 @@ public XceiverClientReply sendCommandAsync( private void invokeXceiverClientGetBlock(XceiverClientSpi client) throws IOException { ContainerProtocolCalls.getBlock(client, - ContainerProtos.DatanodeBlockID.newBuilder() + BlockID.getFromProtobuf(ContainerProtos.DatanodeBlockID.newBuilder() .setContainerID(1) .setLocalID(1) .setBlockCommitSequenceId(1) - .build(), null); + .build()), null, client.getPipeline().getReplicaIndexes()); } private void invokeXceiverClientReadChunk(XceiverClientSpi client) @@ -223,7 +223,7 @@ private void invokeXceiverClientReadChunk(XceiverClientSpi client) .setLen(-1) .setOffset(0) .build(), - bid, + bid.getDatanodeBlockIDProtobuf(), null, null); } diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OMDBCheckpointServlet.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OMDBCheckpointServlet.java index 65584d705852..edfe7ffd87d1 100644 --- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OMDBCheckpointServlet.java +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OMDBCheckpointServlet.java @@ -27,6 +27,7 @@ import org.apache.hadoop.hdds.utils.DBCheckpointServlet; import org.apache.hadoop.hdds.utils.db.DBCheckpoint; import org.apache.hadoop.hdds.utils.db.RDBCheckpointUtils; +import org.apache.hadoop.hdds.utils.db.RDBStore; import org.apache.hadoop.hdds.utils.db.Table; import org.apache.hadoop.hdds.utils.db.TableIterator; import org.apache.hadoop.ozone.OzoneConsts; @@ -317,8 +318,7 @@ private boolean getFilesForArchive(DBCheckpoint checkpoint, // Get the snapshot files. Set snapshotPaths = waitForSnapshotDirs(checkpoint); - Path snapshotDir = Paths.get(OMStorage.getOmDbDir(getConf()).toString(), - OM_SNAPSHOT_DIR); + Path snapshotDir = getSnapshotDir(); if (!processDir(snapshotDir, copyFiles, hardLinkFiles, sstFilesToExclude, snapshotPaths, excluded, copySize, null)) { return false; @@ -635,6 +635,15 @@ private OzoneConfiguration getConf() { .getConfiguration(); } + private Path getSnapshotDir() { + OzoneManager om = (OzoneManager) getServletContext().getAttribute(OzoneConsts.OM_CONTEXT_ATTRIBUTE); + RDBStore store = (RDBStore) om.getMetadataManager().getStore(); + // store.getSnapshotsParentDir() returns path to checkpointState (e.g. /db.snapshots/checkpointState) + // But we need to return path till db.snapshots which contains checkpointState and diffState. + // So that whole snapshots and compaction information can be transferred to follower. + return Paths.get(store.getSnapshotsParentDir()).getParent(); + } + @Override public BootstrapStateHandler.Lock getBootstrapStateLock() { return lock; diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OMMetrics.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OMMetrics.java index faf0efd46433..7003f818bf1a 100644 --- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OMMetrics.java +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OMMetrics.java @@ -26,6 +26,7 @@ import org.apache.hadoop.metrics2.annotation.Metrics; import org.apache.hadoop.metrics2.lib.DefaultMetricsSystem; import org.apache.hadoop.metrics2.lib.MutableCounterLong; +import org.apache.hadoop.metrics2.lib.MutableGaugeInt; /** * This class is for maintaining Ozone Manager statistics. @@ -77,6 +78,7 @@ public class OMMetrics implements OmMetadataReaderMetrics { private @Metric MutableCounterLong numSnapshotPurges; private @Metric MutableCounterLong numSnapshotSetProperties; + private @Metric MutableGaugeInt numSnapshotCacheSize; private @Metric MutableCounterLong numGetFileStatus; private @Metric MutableCounterLong numCreateDirectory; private @Metric MutableCounterLong numCreateFile; @@ -541,6 +543,17 @@ public void decNumSnapshotDeleted() { numSnapshotDeleted.incr(-1); } + public int getNumSnapshotCacheSize() { + return numSnapshotCacheSize.value(); + } + public void incNumSnapshotCacheSize() { + numSnapshotCacheSize.incr(); + } + + public void decNumSnapshotCacheSize() { + numSnapshotCacheSize.decr(); + } + public void incNumCompleteMultipartUploadFails() { numCompleteMultipartUploadFails.incr(); } diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataManagerImpl.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataManagerImpl.java index 56ad604922ad..dd7e749b3e32 100644 --- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataManagerImpl.java +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataManagerImpl.java @@ -88,7 +88,6 @@ import org.apache.hadoop.ozone.om.protocolPB.OzoneManagerProtocolClientSideTranslatorPB; import org.apache.hadoop.ozone.om.request.util.OMMultipartUploadUtils; import org.apache.hadoop.ozone.om.snapshot.ReferenceCounted; -import org.apache.hadoop.ozone.om.snapshot.SnapshotCache; import org.apache.hadoop.ozone.om.snapshot.SnapshotUtils; import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.ExpiredMultipartUploadInfo; import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.ExpiredMultipartUploadsBucket; @@ -108,7 +107,6 @@ import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_FS_SNAPSHOT_MAX_LIMIT_DEFAULT; import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_SNAPSHOT_CHECKPOINT_DIR_CREATION_POLL_TIMEOUT; import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_SNAPSHOT_CHECKPOINT_DIR_CREATION_POLL_TIMEOUT_DEFAULT; -import static org.apache.hadoop.ozone.om.OmSnapshotManager.getSnapshotPrefix; import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.BUCKET_NOT_FOUND; import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.FILE_NOT_FOUND; import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.VOLUME_NOT_FOUND; @@ -1537,7 +1535,7 @@ public PendingKeysDeletion getPendingDeletionKeys(final int keyCount, OmBucketInfo bucketInfo = getBucketTable().get(bucketKey); // Get the latest snapshot in snapshot path. - try (ReferenceCounted + try (ReferenceCounted rcLatestSnapshot = getLatestActiveSnapshot( keySplit[1], keySplit[2], omSnapshotManager)) { @@ -1555,13 +1553,12 @@ public PendingKeysDeletion getPendingDeletionKeys(final int keyCount, if (rcLatestSnapshot != null) { Table prevKeyTable = - ((OmSnapshot) rcLatestSnapshot.get()) + rcLatestSnapshot.get() .getMetadataManager() .getKeyTable(bucketInfo.getBucketLayout()); Table prevDeletedTable = - ((OmSnapshot) rcLatestSnapshot.get()) - .getMetadataManager().getDeletedTable(); + rcLatestSnapshot.get().getMetadataManager().getDeletedTable(); String prevKeyTableDBKey = getSnapshotRenamedTable() .get(dbRenameKey); String prevDelTableDBKey = getOzoneKey(info.getVolumeName(), @@ -1647,8 +1644,7 @@ private boolean versionExistsInPreviousSnapshot(OmKeyInfo omKeyInfo, /** * Get the latest OmSnapshot for a snapshot path. */ - public ReferenceCounted< - IOmMetadataReader, SnapshotCache> getLatestActiveSnapshot( + public ReferenceCounted getLatestActiveSnapshot( String volumeName, String bucketName, OmSnapshotManager snapshotManager) throws IOException { @@ -1682,13 +1678,12 @@ IOmMetadataReader, SnapshotCache> getLatestActiveSnapshot( } } - Optional> rcOmSnapshot = + Optional> rcOmSnapshot = snapshotInfo.isPresent() ? Optional.ofNullable( - snapshotManager.checkForSnapshot(volumeName, + snapshotManager.getSnapshot(volumeName, bucketName, - getSnapshotPrefix(snapshotInfo.get().getName()), - true) + snapshotInfo.get().getName()) ) : Optional.empty(); diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmSnapshot.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmSnapshot.java index 5839c61cf31f..f863c086028e 100644 --- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmSnapshot.java +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmSnapshot.java @@ -47,6 +47,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.UUID; import java.util.stream.Collectors; /** @@ -74,6 +75,7 @@ public class OmSnapshot implements IOmMetadataReader, Closeable { private final String volumeName; private final String bucketName; private final String snapshotName; + private final UUID snapshotID; // To access snapshot checkpoint DB metadata private final OMMetadataManager omMetadataManager; private final KeyManager keyManager; @@ -83,7 +85,8 @@ public OmSnapshot(KeyManager keyManager, OzoneManager ozoneManager, String volumeName, String bucketName, - String snapshotName) { + String snapshotName, + UUID snapshotID) { IAccessAuthorizer accessAuthorizer = OzoneAuthorizerFactory.forSnapshot(ozoneManager, keyManager, prefixManager); @@ -93,6 +96,7 @@ public OmSnapshot(KeyManager keyManager, this.snapshotName = snapshotName; this.bucketName = bucketName; this.volumeName = volumeName; + this.snapshotID = snapshotID; this.keyManager = keyManager; this.omMetadataManager = keyManager.getMetadataManager(); } @@ -295,6 +299,10 @@ public String getName() { return snapshotName; } + public UUID getSnapshotID() { + return snapshotID; + } + @Override public void close() throws IOException { // Close DB diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmSnapshotManager.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmSnapshotManager.java index 6e9af3960cae..9d8352d2834f 100644 --- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmSnapshotManager.java +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmSnapshotManager.java @@ -35,6 +35,7 @@ import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import java.util.UUID; import com.google.common.cache.RemovalListener; import org.apache.hadoop.hdds.StringUtils; @@ -82,6 +83,8 @@ import static org.apache.hadoop.ozone.OzoneConsts.OM_SNAPSHOT_CHECKPOINT_DIR; import static org.apache.hadoop.ozone.OzoneConsts.OM_SNAPSHOT_DIFF_DB_NAME; import static org.apache.hadoop.ozone.OzoneConsts.OM_SNAPSHOT_INDICATOR; +import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_SNAPSHOT_CACHE_CLEANUP_SERVICE_RUN_INTERVAL; +import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_SNAPSHOT_CACHE_CLEANUP_SERVICE_RUN_INTERVAL_DEFAULT; import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_SNAPSHOT_CACHE_MAX_SIZE; import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_SNAPSHOT_CACHE_MAX_SIZE_DEFAULT; import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_SNAPSHOT_DB_MAX_OPEN_FILES; @@ -244,7 +247,7 @@ public OmSnapshotManager(OzoneManager ozoneManager) { OZONE_OM_SNAPSHOT_CACHE_MAX_SIZE, OZONE_OM_SNAPSHOT_CACHE_MAX_SIZE_DEFAULT); - CacheLoader loader = createCacheLoader(); + CacheLoader loader = createCacheLoader(); // TODO: [SNAPSHOT] Remove this if not going to make SnapshotCache impl // pluggable. @@ -271,10 +274,15 @@ public OmSnapshotManager(OzoneManager ozoneManager) { }; // Init snapshot cache - this.snapshotCache = new SnapshotCache(this, loader, softCacheSize); + long cacheCleanupServiceInterval = ozoneManager.getConfiguration() + .getTimeDuration(OZONE_OM_SNAPSHOT_CACHE_CLEANUP_SERVICE_RUN_INTERVAL, + OZONE_OM_SNAPSHOT_CACHE_CLEANUP_SERVICE_RUN_INTERVAL_DEFAULT, + TimeUnit.MILLISECONDS); + this.snapshotCache = new SnapshotCache(loader, softCacheSize, ozoneManager.getMetrics(), + cacheCleanupServiceInterval); this.snapshotDiffManager = new SnapshotDiffManager(snapshotDiffDb, differ, - ozoneManager, snapshotCache, snapDiffJobCf, snapDiffReportCf, + ozoneManager, snapDiffJobCf, snapDiffReportCf, columnFamilyOptions, codecRegistry); diffCleanupServiceInterval = ozoneManager.getConfiguration() @@ -325,19 +333,25 @@ public boolean canDisableFsSnapshot(OMMetadataManager ommm) { return isSnapshotInfoTableEmpty; } - private CacheLoader createCacheLoader() { - return new CacheLoader() { + private CacheLoader createCacheLoader() { + return new CacheLoader() { @Nonnull @Override - public OmSnapshot load(@Nonnull String snapshotTableKey) - throws IOException { - // Check if the snapshot exists - final SnapshotInfo snapshotInfo = getSnapshotInfo(snapshotTableKey); + public OmSnapshot load(@Nonnull UUID snapshotId) throws IOException { + String snapshotTableKey = ((OmMetadataManagerImpl) ozoneManager.getMetadataManager()) + .getSnapshotChainManager() + .getTableKey(snapshotId); + + // SnapshotChain maintains in-memory reverse mapping of snapshotId to snapshotName based on snapshotInfoTable. + // So it should not happen ideally. + // If it happens, then either snapshot has been purged in between or SnapshotChain is corrupted + // and missing some entries which needs investigation. + if (snapshotTableKey == null) { + throw new IOException("No snapshot exist with snapshotId: " + snapshotId); + } - // Block snapshot from loading when it is no longer active e.g. DELETED, - // unless this is called from SnapshotDeletingService. - checkSnapshotActive(snapshotInfo, true); + final SnapshotInfo snapshotInfo = getSnapshotInfo(snapshotTableKey); CacheValue cacheValue = ozoneManager.getMetadataManager() .getSnapshotInfoTable() @@ -375,7 +389,8 @@ public OmSnapshot load(@Nonnull String snapshotTableKey) return new OmSnapshot(km, pm, ozoneManager, snapshotInfo.getVolumeName(), snapshotInfo.getBucketName(), - snapshotInfo.getName()); + snapshotInfo.getName(), + snapshotInfo.getSnapshotId()); } catch (Exception e) { // Close RocksDB if there is any failure. if (!snapshotMetadataManager.getStore().isClosed()) { @@ -397,11 +412,32 @@ private static CodecRegistry createCodecRegistryForSnapDiff() { } /** - * Get snapshot instance LRU cache. - * @return LoadingCache + * Get snapshot instance LRU cache size. + * @return cache size. */ - public SnapshotCache getSnapshotCache() { - return snapshotCache; + @VisibleForTesting + public int getSnapshotCacheSize() { + return snapshotCache == null ? 0 : snapshotCache.size(); + } + + /** + * Immediately invalidate all entries and close their DB instances in cache. + */ + public void invalidateCache() { + if (snapshotCache != null) { + snapshotCache.invalidateAll(); + } + } + + /** + * Immediately invalidate an entry. + * + * @param key SnapshotId. + */ + public void invalidateCacheEntry(UUID key) { + if (snapshotCache != null) { + snapshotCache.invalidate(key); + } } /** @@ -590,11 +626,11 @@ private static void deleteKeysFromDelKeyTableInSnapshotScope( } // Get OmSnapshot if the keyName has ".snapshot" key indicator - public ReferenceCounted checkForSnapshot( + @SuppressWarnings("unchecked") + public ReferenceCounted getActiveFsMetadataOrSnapshot( String volumeName, String bucketName, - String keyName, - boolean skipActiveCheck) throws IOException { + String keyName) throws IOException { if (keyName == null || !ozoneManager.isFilesystemSnapshotEnabled()) { return ozoneManager.getOmMetadataReader(); } @@ -603,31 +639,57 @@ public ReferenceCounted checkForSnapshot( String[] keyParts = keyName.split(OM_KEY_PREFIX); if (isSnapshotKey(keyParts)) { String snapshotName = keyParts[1]; - if (snapshotName == null || snapshotName.isEmpty()) { - // don't allow snapshot indicator without snapshot name - throw new OMException(INVALID_KEY_NAME); - } - String snapshotTableKey = SnapshotInfo.getTableKey(volumeName, - bucketName, snapshotName); - - // Block FS API reads when snapshot is not active. - if (!skipActiveCheck) { - checkSnapshotActive(ozoneManager, snapshotTableKey); - } - // Warn if actual cache size exceeds the soft limit already. - if (snapshotCache.size() > softCacheSize) { - LOG.warn("Snapshot cache size ({}) exceeds configured soft-limit ({}).", - snapshotCache.size(), softCacheSize); - } - - // retrieve the snapshot from the cache - return snapshotCache.get(snapshotTableKey, skipActiveCheck); + return (ReferenceCounted) (ReferenceCounted) + getActiveSnapshot(volumeName, bucketName, snapshotName); } else { return ozoneManager.getOmMetadataReader(); } } + public ReferenceCounted getActiveSnapshot( + String volumeName, + String bucketName, + String snapshotName) throws IOException { + return getSnapshot(volumeName, bucketName, snapshotName, false); + } + + public ReferenceCounted getSnapshot( + String volumeName, + String bucketName, + String snapshotName) throws IOException { + return getSnapshot(volumeName, bucketName, snapshotName, true); + } + + public ReferenceCounted getSnapshot( + String volumeName, + String bucketName, + String snapshotName, + boolean skipActiveCheck) throws IOException { + + if (snapshotName == null || snapshotName.isEmpty()) { + // don't allow snapshot indicator without snapshot name + throw new OMException(INVALID_KEY_NAME); + } + + String snapshotTableKey = SnapshotInfo.getTableKey(volumeName, + bucketName, snapshotName); + + return getSnapshot(snapshotTableKey, skipActiveCheck); + } + + private ReferenceCounted getSnapshot(String snapshotTableKey, boolean skipActiveCheck) + throws IOException { + SnapshotInfo snapshotInfo = SnapshotUtils.getSnapshotInfo(ozoneManager, snapshotTableKey); + // Block FS API reads when snapshot is not active. + if (!skipActiveCheck) { + checkSnapshotActive(snapshotInfo, false); + } + + // retrieve the snapshot from the cache + return snapshotCache.get(snapshotInfo.getSnapshotId()); + } + /** * Returns true if the snapshot is in given status. * @param key DB snapshot table key @@ -894,9 +956,11 @@ public void close() { if (snapshotDiffManager != null) { snapshotDiffManager.close(); } + if (snapshotCache != null) { - snapshotCache.invalidateAll(); + snapshotCache.close(); } + if (snapshotDiffCleanupService != null) { snapshotDiffCleanupService.shutdown(); } diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneAclUtils.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneAclUtils.java index cb42a0a881c4..ed2527e03020 100644 --- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneAclUtils.java +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneAclUtils.java @@ -83,8 +83,6 @@ public static void checkAllAcls(OmMetadataReader omMetadataReader, String bucketOwner, UserGroupInformation user, InetAddress remoteAddress, String hostName) throws IOException { - boolean isVolOwner = isOwner(user, volOwner); - switch (resType) { //For Volume level access we only need to check {OWNER} equal // to Volume Owner. @@ -100,7 +98,7 @@ public static void checkAllAcls(OmMetadataReader omMetadataReader, // volume owner if current ugi user is volume owner else we need check //{OWNER} equals bucket owner for bucket/key/prefix. case PREFIX: - if (isVolOwner) { + if (isOwner(user, volOwner)) { omMetadataReader.checkAcls(resType, storeType, aclType, vol, bucket, key, user, remoteAddress, hostName, true, @@ -184,12 +182,6 @@ public static IAccessAuthorizer.ACLType getParentNativeAcl( private static boolean isOwner(UserGroupInformation callerUgi, String ownerName) { - if (ownerName == null) { - return false; - } - if (callerUgi.getShortUserName().equals(ownerName)) { - return true; - } - return false; + return ownerName != null && ownerName.equals(callerUgi.getShortUserName()); } } diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java index 053afbed3c56..0037fbb1b5d0 100644 --- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java @@ -99,7 +99,6 @@ import org.apache.hadoop.ozone.om.service.OMRangerBGSyncService; import org.apache.hadoop.ozone.om.snapshot.OmSnapshotUtils; import org.apache.hadoop.ozone.om.snapshot.ReferenceCounted; -import org.apache.hadoop.ozone.om.snapshot.SnapshotCache; import org.apache.hadoop.ozone.om.upgrade.OMLayoutFeature; import org.apache.hadoop.ozone.security.acl.OzoneAuthorizerFactory; import org.apache.hadoop.ozone.snapshot.CancelSnapshotDiffResponse; @@ -486,7 +485,7 @@ private enum State { private OmMetadataReader omMetadataReader; // Wrap active DB metadata reader in ReferenceCounted once to avoid // instance creation every single time. - private ReferenceCounted rcOmMetadataReader; + private ReferenceCounted rcOmMetadataReader; private OmSnapshotManager omSnapshotManager; @SuppressWarnings("methodlength") @@ -2580,8 +2579,7 @@ public boolean getAllowListAllVolumes() { return allowListAllVolumes; } - public ReferenceCounted< - IOmMetadataReader, SnapshotCache> getOmMetadataReader() { + public ReferenceCounted getOmMetadataReader() { return rcOmMetadataReader; } @@ -2851,8 +2849,7 @@ public OmBucketInfo getBucketInfo(String volume, String bucket) */ @Override public OmKeyInfo lookupKey(OmKeyArgs args) throws IOException { - try (ReferenceCounted - rcReader = getReader(args)) { + try (ReferenceCounted rcReader = getReader(args)) { return rcReader.get().lookupKey(args); } } @@ -2864,8 +2861,7 @@ public OmKeyInfo lookupKey(OmKeyArgs args) throws IOException { public KeyInfoWithVolumeContext getKeyInfo(final OmKeyArgs args, boolean assumeS3Context) throws IOException { - try (ReferenceCounted rcReader = - getReader(args)) { + try (ReferenceCounted rcReader = getReader(args)) { return rcReader.get().getKeyInfo(args, assumeS3Context); } } @@ -2877,7 +2873,7 @@ public KeyInfoWithVolumeContext getKeyInfo(final OmKeyArgs args, public ListKeysResult listKeys(String volumeName, String bucketName, String startKey, String keyPrefix, int maxKeys) throws IOException { - try (ReferenceCounted rcReader = + try (ReferenceCounted rcReader = getReader(volumeName, bucketName, keyPrefix)) { return rcReader.get().listKeys( volumeName, bucketName, startKey, keyPrefix, maxKeys); @@ -3637,7 +3633,7 @@ public OmMultipartUploadList listMultipartUploads(String volumeName, */ @Override public OzoneFileStatus getFileStatus(OmKeyArgs args) throws IOException { - try (ReferenceCounted rcReader = + try (ReferenceCounted rcReader = getReader(args)) { return rcReader.get().getFileStatus(args); } @@ -3648,7 +3644,7 @@ public OzoneFileStatus getFileStatus(OmKeyArgs args) throws IOException { */ @Override public OmKeyInfo lookupFile(OmKeyArgs args) throws IOException { - try (ReferenceCounted rcReader = + try (ReferenceCounted rcReader = getReader(args)) { return rcReader.get().lookupFile(args); } @@ -3667,7 +3663,7 @@ public List listStatus(OmKeyArgs args, boolean recursive, public List listStatus(OmKeyArgs args, boolean recursive, String startKey, long numEntries, boolean allowPartialPrefixes) throws IOException { - try (ReferenceCounted rcReader = + try (ReferenceCounted rcReader = getReader(args)) { return rcReader.get().listStatus( args, recursive, startKey, numEntries, allowPartialPrefixes); @@ -3691,7 +3687,7 @@ public List listStatusLight(OmKeyArgs args, */ @Override public List getAcl(OzoneObj obj) throws IOException { - try (ReferenceCounted rcReader = + try (ReferenceCounted rcReader = getReader(obj)) { return rcReader.get().getAcl(obj); } @@ -3759,7 +3755,7 @@ TermIndex installCheckpoint(String leaderId, Path checkpointLocation, keyManager.stop(); stopSecretManager(); stopTrashEmptier(); - omSnapshotManager.getSnapshotCache().invalidateAll(); + omSnapshotManager.invalidateCache(); // Pause the State Machine so that no new transactions can be applied. // This action also clears the OM Double Buffer so that if there are any // pending transactions in the buffer, they are discarded. @@ -4729,12 +4725,10 @@ public static HddsProtos.OzoneManagerDetailsProto getOmDetailsProto( * @param keyArgs OmKeyArgs * @return ReferenceCounted */ - private ReferenceCounted< - IOmMetadataReader, SnapshotCache> getReader(OmKeyArgs keyArgs) + private ReferenceCounted getReader(OmKeyArgs keyArgs) throws IOException { - return omSnapshotManager.checkForSnapshot( - keyArgs.getVolumeName(), keyArgs.getBucketName(), keyArgs.getKeyName(), - false); + return omSnapshotManager.getActiveFsMetadataOrSnapshot( + keyArgs.getVolumeName(), keyArgs.getBucketName(), keyArgs.getKeyName()); } /** @@ -4746,11 +4740,10 @@ IOmMetadataReader, SnapshotCache> getReader(OmKeyArgs keyArgs) * @param key key path * @return ReferenceCounted */ - private ReferenceCounted< - IOmMetadataReader, SnapshotCache> getReader( + private ReferenceCounted getReader( String volumeName, String bucketName, String key) throws IOException { - return omSnapshotManager.checkForSnapshot( - volumeName, bucketName, key, false); + return omSnapshotManager.getActiveFsMetadataOrSnapshot( + volumeName, bucketName, key); } /** @@ -4760,14 +4753,12 @@ IOmMetadataReader, SnapshotCache> getReader( * @param ozoneObj OzoneObj * @return ReferenceCounted */ - private ReferenceCounted< - IOmMetadataReader, SnapshotCache> getReader(OzoneObj ozoneObj) + private ReferenceCounted getReader(OzoneObj ozoneObj) throws IOException { - return omSnapshotManager.checkForSnapshot( + return omSnapshotManager.getActiveFsMetadataOrSnapshot( ozoneObj.getVolumeName(), ozoneObj.getBucketName(), - ozoneObj.getKeyName(), - false); + ozoneObj.getKeyName()); } @SuppressWarnings("parameternumber") diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/SstFilteringService.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/SstFilteringService.java index cae9bc4b3fca..20d0ab0e53eb 100644 --- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/SstFilteringService.java +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/SstFilteringService.java @@ -33,7 +33,6 @@ import org.apache.hadoop.ozone.om.helpers.SnapshotInfo; import org.apache.hadoop.ozone.om.lock.OMLockDetails; import org.apache.hadoop.ozone.om.snapshot.ReferenceCounted; -import org.apache.hadoop.ozone.om.snapshot.SnapshotCache; import org.rocksdb.RocksDBException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -147,10 +146,9 @@ private void markSSTFilteredFlagForSnapshot(String volume, String bucket, @Override public BackgroundTaskResult call() throws Exception { - Optional snapshotCache = Optional.ofNullable(ozoneManager) - .map(OzoneManager::getOmSnapshotManager) - .map(OmSnapshotManager::getSnapshotCache); - if (!snapshotCache.isPresent()) { + Optional snapshotManager = Optional.ofNullable(ozoneManager) + .map(OzoneManager::getOmSnapshotManager); + if (!snapshotManager.isPresent()) { return BackgroundTaskResult.EmptyTaskResult.newResult(); } Table snapshotInfoTable = @@ -183,10 +181,12 @@ public BackgroundTaskResult call() throws Exception { snapshotInfo.getBucketName()); try ( - ReferenceCounted - snapshotMetadataReader = snapshotCache.get().get( - snapshotInfo.getTableKey())) { - OmSnapshot omSnapshot = (OmSnapshot) snapshotMetadataReader.get(); + ReferenceCounted snapshotMetadataReader = + snapshotManager.get().getActiveSnapshot( + snapshotInfo.getVolumeName(), + snapshotInfo.getBucketName(), + snapshotInfo.getName())) { + OmSnapshot omSnapshot = snapshotMetadataReader.get(); RDBStore rdbStore = (RDBStore) omSnapshot.getMetadataManager() .getStore(); RocksDatabase db = rdbStore.getDb(); diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/OMClientRequest.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/OMClientRequest.java index 1d897b89aa28..5c07d9eca128 100644 --- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/OMClientRequest.java +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/OMClientRequest.java @@ -40,7 +40,6 @@ import org.apache.hadoop.ozone.om.ratis.utils.OzoneManagerRatisUtils; import org.apache.hadoop.ozone.om.response.OMClientResponse; import org.apache.hadoop.ozone.om.snapshot.ReferenceCounted; -import org.apache.hadoop.ozone.om.snapshot.SnapshotCache; import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos; import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.LayoutVersion; import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest; @@ -289,7 +288,7 @@ protected void checkACLsWithFSO(OzoneManager ozoneManager, String volumeName, contextBuilder.setOwnerName(bucketOwner); } - try (ReferenceCounted rcMetadataReader = + try (ReferenceCounted rcMetadataReader = ozoneManager.getOmMetadataReader()) { OmMetadataReader omMetadataReader = (OmMetadataReader) rcMetadataReader.get(); @@ -355,7 +354,7 @@ public void checkAcls(OzoneManager ozoneManager, String bucketOwner) throws IOException { - try (ReferenceCounted rcMetadataReader = + try (ReferenceCounted rcMetadataReader = ozoneManager.getOmMetadataReader()) { OzoneAclUtils.checkAllAcls((OmMetadataReader) rcMetadataReader.get(), resType, storeType, aclType, diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/snapshot/OMSnapshotPurgeRequest.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/snapshot/OMSnapshotPurgeRequest.java index b68e7f6079b2..ed4842c7eab1 100644 --- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/snapshot/OMSnapshotPurgeRequest.java +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/snapshot/OMSnapshotPurgeRequest.java @@ -136,8 +136,8 @@ public OMClientResponse validateAndUpdateCache(OzoneManager ozoneManager, updatedPathPreviousAndGlobalSnapshots); updateSnapshotInfoAndCache(nextSnapshot, omMetadataManager, trxnLogIndex, updatedSnapInfos); // Remove and close snapshot's RocksDB instance from SnapshotCache. - omSnapshotManager.getSnapshotCache().invalidate(snapTableKey); - // Update SnapshotInfoTable cache. + omSnapshotManager.invalidateCacheEntry(fromSnapshot.getSnapshotId()); + // Update SnapshotInfoTable cache. omMetadataManager.getSnapshotInfoTable() .addCacheEntry(new CacheKey<>(fromSnapshot.getTableKey()), CacheValue.get(trxnLogIndex)); } finally { diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/key/OMDirectoriesPurgeResponseWithFSO.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/key/OMDirectoriesPurgeResponseWithFSO.java index 758133b41113..5d3875baaf87 100644 --- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/key/OMDirectoriesPurgeResponseWithFSO.java +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/key/OMDirectoriesPurgeResponseWithFSO.java @@ -23,7 +23,6 @@ import org.apache.hadoop.hdds.utils.db.BatchOperation; import org.apache.hadoop.hdds.utils.db.DBStore; import org.apache.hadoop.ozone.OmUtils; -import org.apache.hadoop.ozone.om.IOmMetadataReader; import org.apache.hadoop.ozone.om.OMMetadataManager; import org.apache.hadoop.ozone.om.OmMetadataManagerImpl; import org.apache.hadoop.ozone.om.OmSnapshot; @@ -36,7 +35,6 @@ import org.apache.hadoop.ozone.om.request.key.OMDirectoriesPurgeRequestWithFSO; import org.apache.hadoop.ozone.om.response.CleanupTableInfo; import org.apache.hadoop.ozone.om.snapshot.ReferenceCounted; -import org.apache.hadoop.ozone.om.snapshot.SnapshotCache; import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos; import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMResponse; import org.slf4j.Logger; @@ -50,7 +48,6 @@ import static org.apache.hadoop.ozone.om.OmMetadataManagerImpl.DELETED_TABLE; import static org.apache.hadoop.ozone.om.OmMetadataManagerImpl.DIRECTORY_TABLE; import static org.apache.hadoop.ozone.om.OmMetadataManagerImpl.FILE_TABLE; -import static org.apache.hadoop.ozone.om.OmSnapshotManager.getSnapshotPrefix; /** * Response for {@link OMDirectoriesPurgeRequestWithFSO} request. @@ -86,13 +83,12 @@ public void addToDBBatch(OMMetadataManager metadataManager, ((OmMetadataManagerImpl) metadataManager) .getOzoneManager().getOmSnapshotManager(); - try (ReferenceCounted - rcFromSnapshotInfo = omSnapshotManager.checkForSnapshot( + try (ReferenceCounted + rcFromSnapshotInfo = omSnapshotManager.getSnapshot( fromSnapshotInfo.getVolumeName(), fromSnapshotInfo.getBucketName(), - getSnapshotPrefix(fromSnapshotInfo.getName()), - true)) { - OmSnapshot fromSnapshot = (OmSnapshot) rcFromSnapshotInfo.get(); + fromSnapshotInfo.getName())) { + OmSnapshot fromSnapshot = rcFromSnapshotInfo.get(); DBStore fromSnapshotStore = fromSnapshot.getMetadataManager() .getStore(); // Init Batch Operation for snapshot db. diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/key/OMKeyPurgeResponse.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/key/OMKeyPurgeResponse.java index 719ece21c151..b606b610daec 100644 --- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/key/OMKeyPurgeResponse.java +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/key/OMKeyPurgeResponse.java @@ -19,7 +19,6 @@ package org.apache.hadoop.ozone.om.response.key; import org.apache.hadoop.hdds.utils.db.DBStore; -import org.apache.hadoop.ozone.om.IOmMetadataReader; import org.apache.hadoop.ozone.om.OMMetadataManager; import org.apache.hadoop.ozone.om.OmMetadataManagerImpl; import org.apache.hadoop.ozone.om.OmSnapshot; @@ -29,7 +28,6 @@ import org.apache.hadoop.ozone.om.response.CleanupTableInfo; import org.apache.hadoop.ozone.om.request.key.OMKeyPurgeRequest; import org.apache.hadoop.ozone.om.snapshot.ReferenceCounted; -import org.apache.hadoop.ozone.om.snapshot.SnapshotCache; import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.KeyInfo; import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMResponse; import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.SnapshotMoveKeyInfos; @@ -41,7 +39,6 @@ import javax.annotation.Nonnull; import static org.apache.hadoop.ozone.om.OmMetadataManagerImpl.DELETED_TABLE; -import static org.apache.hadoop.ozone.om.OmSnapshotManager.getSnapshotPrefix; import static org.apache.hadoop.ozone.om.response.snapshot.OMSnapshotMoveDeletedKeysResponse.createRepeatedOmKeyInfo; /** @@ -81,14 +78,13 @@ public void addToDBBatch(OMMetadataManager omMetadataManager, ((OmMetadataManagerImpl) omMetadataManager) .getOzoneManager().getOmSnapshotManager(); - try (ReferenceCounted rcOmFromSnapshot = - omSnapshotManager.checkForSnapshot( + try (ReferenceCounted rcOmFromSnapshot = + omSnapshotManager.getSnapshot( fromSnapshot.getVolumeName(), fromSnapshot.getBucketName(), - getSnapshotPrefix(fromSnapshot.getName()), - true)) { + fromSnapshot.getName())) { - OmSnapshot fromOmSnapshot = (OmSnapshot) rcOmFromSnapshot.get(); + OmSnapshot fromOmSnapshot = rcOmFromSnapshot.get(); DBStore fromSnapshotStore = fromOmSnapshot.getMetadataManager().getStore(); // Init Batch Operation for snapshot db. diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/snapshot/OMSnapshotMoveDeletedKeysResponse.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/snapshot/OMSnapshotMoveDeletedKeysResponse.java index f4142400d7cf..043b647e088b 100644 --- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/snapshot/OMSnapshotMoveDeletedKeysResponse.java +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/snapshot/OMSnapshotMoveDeletedKeysResponse.java @@ -21,7 +21,6 @@ import org.apache.hadoop.hdds.protocol.proto.HddsProtos; import org.apache.hadoop.hdds.utils.db.BatchOperation; import org.apache.hadoop.hdds.utils.db.RDBStore; -import org.apache.hadoop.ozone.om.IOmMetadataReader; import org.apache.hadoop.ozone.om.OMMetadataManager; import org.apache.hadoop.ozone.om.OmMetadataManagerImpl; import org.apache.hadoop.ozone.om.OmSnapshot; @@ -32,7 +31,6 @@ import org.apache.hadoop.ozone.om.response.CleanupTableInfo; import org.apache.hadoop.ozone.om.response.OMClientResponse; import org.apache.hadoop.ozone.om.snapshot.ReferenceCounted; -import org.apache.hadoop.ozone.om.snapshot.SnapshotCache; import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.KeyInfo; import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.SnapshotMoveKeyInfos; import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMResponse; @@ -42,7 +40,6 @@ import java.util.List; import static org.apache.hadoop.ozone.om.OmMetadataManagerImpl.SNAPSHOT_INFO_TABLE; -import static org.apache.hadoop.ozone.om.OmSnapshotManager.getSnapshotPrefix; /** * Response for OMSnapshotMoveDeletedKeysRequest. @@ -93,24 +90,22 @@ protected void addToDBBatch(OMMetadataManager omMetadataManager, ((OmMetadataManagerImpl) omMetadataManager) .getOzoneManager().getOmSnapshotManager(); - try (ReferenceCounted rcOmFromSnapshot = - omSnapshotManager.checkForSnapshot( + try (ReferenceCounted rcOmFromSnapshot = + omSnapshotManager.getSnapshot( fromSnapshot.getVolumeName(), fromSnapshot.getBucketName(), - getSnapshotPrefix(fromSnapshot.getName()), - true)) { + fromSnapshot.getName())) { - OmSnapshot fromOmSnapshot = (OmSnapshot) rcOmFromSnapshot.get(); + OmSnapshot fromOmSnapshot = rcOmFromSnapshot.get(); if (nextSnapshot != null) { - try (ReferenceCounted - rcOmNextSnapshot = omSnapshotManager.checkForSnapshot( + try (ReferenceCounted + rcOmNextSnapshot = omSnapshotManager.getSnapshot( nextSnapshot.getVolumeName(), nextSnapshot.getBucketName(), - getSnapshotPrefix(nextSnapshot.getName()), - true)) { + nextSnapshot.getName())) { - OmSnapshot nextOmSnapshot = (OmSnapshot) rcOmNextSnapshot.get(); + OmSnapshot nextOmSnapshot = rcOmNextSnapshot.get(); RDBStore nextSnapshotStore = (RDBStore) nextOmSnapshot.getMetadataManager().getStore(); // Init Batch Operation for snapshot db. diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/DirectoryDeletingService.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/DirectoryDeletingService.java index 9643fa82969c..d7205b2c1bbf 100644 --- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/DirectoryDeletingService.java +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/DirectoryDeletingService.java @@ -26,7 +26,6 @@ import org.apache.hadoop.hdds.utils.db.Table; import org.apache.hadoop.hdds.utils.db.Table.KeyValue; import org.apache.hadoop.hdds.utils.db.TableIterator; -import org.apache.hadoop.ozone.om.IOmMetadataReader; import org.apache.hadoop.ozone.om.OMConfigKeys; import org.apache.hadoop.ozone.om.OmMetadataManagerImpl; import org.apache.hadoop.ozone.om.OmSnapshot; @@ -35,7 +34,6 @@ import org.apache.hadoop.ozone.om.helpers.OmDirectoryInfo; import org.apache.hadoop.ozone.om.helpers.OmKeyInfo; import org.apache.hadoop.ozone.om.snapshot.ReferenceCounted; -import org.apache.hadoop.ozone.om.snapshot.SnapshotCache; import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.PurgePathRequest; import org.apache.hadoop.util.Time; import org.apache.ratis.protocol.ClientId; @@ -238,7 +236,7 @@ private boolean previousSnapshotHasDir( OmMetadataManagerImpl metadataManager = (OmMetadataManagerImpl) getOzoneManager().getMetadataManager(); - try (ReferenceCounted rcLatestSnapshot = + try (ReferenceCounted rcLatestSnapshot = metadataManager.getLatestActiveSnapshot( deletedDirInfo.getVolumeName(), deletedDirInfo.getBucketName(), @@ -249,11 +247,9 @@ private boolean previousSnapshotHasDir( .getRenameKey(deletedDirInfo.getVolumeName(), deletedDirInfo.getBucketName(), deletedDirInfo.getObjectID()); Table prevDirTable = - ((OmSnapshot) rcLatestSnapshot.get()) - .getMetadataManager().getDirectoryTable(); + rcLatestSnapshot.get().getMetadataManager().getDirectoryTable(); Table prevDeletedDirTable = - ((OmSnapshot) rcLatestSnapshot.get()) - .getMetadataManager().getDeletedDirTable(); + rcLatestSnapshot.get().getMetadataManager().getDeletedDirTable(); OmKeyInfo prevDeletedDirInfo = prevDeletedDirTable.get(key); if (prevDeletedDirInfo != null) { return true; diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/KeyDeletingService.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/KeyDeletingService.java index e89608e82db2..83991668c9f3 100644 --- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/KeyDeletingService.java +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/KeyDeletingService.java @@ -37,7 +37,6 @@ import org.apache.hadoop.hdds.utils.db.TableIterator; import org.apache.hadoop.ozone.OzoneConsts; import org.apache.hadoop.ozone.common.BlockGroup; -import org.apache.hadoop.ozone.om.IOmMetadataReader; import org.apache.hadoop.ozone.om.KeyManager; import org.apache.hadoop.ozone.om.OmMetadataManagerImpl; import org.apache.hadoop.ozone.om.OmSnapshot; @@ -46,7 +45,6 @@ import org.apache.hadoop.ozone.om.helpers.OMRatisHelper; import org.apache.hadoop.ozone.om.ratis.OzoneManagerRatisServer; import org.apache.hadoop.ozone.om.snapshot.ReferenceCounted; -import org.apache.hadoop.ozone.om.snapshot.SnapshotCache; import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest; import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.SnapshotSize; import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.SetSnapshotPropertyRequest; @@ -58,7 +56,6 @@ import com.google.common.annotations.VisibleForTesting; -import static org.apache.hadoop.ozone.om.OmSnapshotManager.getSnapshotPrefix; import static org.apache.hadoop.ozone.om.helpers.SnapshotInfo.SnapshotStatus.SNAPSHOT_ACTIVE; import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_KEY_DELETING_LIMIT_PER_TASK; import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_KEY_DELETING_LIMIT_PER_TASK_DEFAULT; @@ -264,13 +261,12 @@ private void processSnapshotDeepClean(int delCount) continue; } - try (ReferenceCounted - rcCurrOmSnapshot = omSnapshotManager.checkForSnapshot( + try (ReferenceCounted + rcCurrOmSnapshot = omSnapshotManager.getSnapshot( currSnapInfo.getVolumeName(), currSnapInfo.getBucketName(), - getSnapshotPrefix(currSnapInfo.getName()), - true)) { - OmSnapshot currOmSnapshot = (OmSnapshot) rcCurrOmSnapshot.get(); + currSnapInfo.getName())) { + OmSnapshot currOmSnapshot = rcCurrOmSnapshot.get(); Table snapDeletedTable = currOmSnapshot.getMetadataManager().getDeletedTable(); @@ -304,18 +300,16 @@ private void processSnapshotDeepClean(int delCount) Table previousKeyTable = null; Table prevRenamedTable = null; - ReferenceCounted - rcPrevOmSnapshot = null; + ReferenceCounted rcPrevOmSnapshot = null; // Split RepeatedOmKeyInfo and update current snapshot // deletedKeyTable and next snapshot deletedKeyTable. if (previousSnapshot != null) { - rcPrevOmSnapshot = omSnapshotManager.checkForSnapshot( + rcPrevOmSnapshot = omSnapshotManager.getSnapshot( previousSnapshot.getVolumeName(), previousSnapshot.getBucketName(), - getSnapshotPrefix(previousSnapshot.getName()), true); - OmSnapshot omPreviousSnapshot = (OmSnapshot) - rcPrevOmSnapshot.get(); + previousSnapshot.getName()); + OmSnapshot omPreviousSnapshot = rcPrevOmSnapshot.get(); previousKeyTable = omPreviousSnapshot.getMetadataManager() .getKeyTable(bucketInfo.getBucketLayout()); @@ -324,15 +318,13 @@ private void processSnapshotDeepClean(int delCount) } Table previousToPrevKeyTable = null; - ReferenceCounted - rcPrevToPrevOmSnapshot = null; + ReferenceCounted rcPrevToPrevOmSnapshot = null; if (previousToPrevSnapshot != null) { - rcPrevToPrevOmSnapshot = omSnapshotManager.checkForSnapshot( + rcPrevToPrevOmSnapshot = omSnapshotManager.getSnapshot( previousToPrevSnapshot.getVolumeName(), previousToPrevSnapshot.getBucketName(), - getSnapshotPrefix(previousToPrevSnapshot.getName()), true); - OmSnapshot omPreviousToPrevSnapshot = (OmSnapshot) - rcPrevToPrevOmSnapshot.get(); + previousToPrevSnapshot.getName()); + OmSnapshot omPreviousToPrevSnapshot = rcPrevToPrevOmSnapshot.get(); previousToPrevKeyTable = omPreviousToPrevSnapshot .getMetadataManager() diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/SnapshotDeletingService.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/SnapshotDeletingService.java index cc275b4e8e6a..29b2b319532b 100644 --- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/SnapshotDeletingService.java +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/SnapshotDeletingService.java @@ -34,7 +34,6 @@ import org.apache.hadoop.ozone.OzoneConsts; import org.apache.hadoop.ozone.common.BlockGroup; import org.apache.hadoop.ozone.lock.BootstrapStateHandler; -import org.apache.hadoop.ozone.om.IOmMetadataReader; import org.apache.hadoop.ozone.om.OMConfigKeys; import org.apache.hadoop.ozone.om.OmMetadataManagerImpl; import org.apache.hadoop.ozone.om.KeyManagerImpl; @@ -52,7 +51,6 @@ import org.apache.hadoop.ozone.om.helpers.SnapshotInfo; import org.apache.hadoop.ozone.om.ratis.OzoneManagerRatisServer; import org.apache.hadoop.ozone.om.snapshot.ReferenceCounted; -import org.apache.hadoop.ozone.om.snapshot.SnapshotCache; import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest; import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.PurgePathRequest; import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.SnapshotMoveDeletedKeysRequest; @@ -78,7 +76,6 @@ import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_SNAPSHOT_KEY_DELETING_LIMIT_PER_TASK_DEFAULT; import static org.apache.hadoop.ozone.om.OMConfigKeys.SNAPSHOT_DELETING_LIMIT_PER_TASK; import static org.apache.hadoop.ozone.om.OMConfigKeys.SNAPSHOT_DELETING_LIMIT_PER_TASK_DEFAULT; -import static org.apache.hadoop.ozone.om.OmSnapshotManager.getSnapshotPrefix; /** * Background Service to clean-up deleted snapshot and reclaim space. @@ -143,10 +140,8 @@ public BackgroundTaskResult call() throws InterruptedException { getRunCount().incrementAndGet(); - ReferenceCounted rcOmSnapshot = - null; - ReferenceCounted rcOmPreviousSnapshot = - null; + ReferenceCounted rcOmSnapshot = null; + ReferenceCounted rcOmPreviousSnapshot = null; Table snapshotInfoTable = ozoneManager.getMetadataManager().getSnapshotInfoTable(); @@ -169,12 +164,11 @@ public BackgroundTaskResult call() throws InterruptedException { // Note: Can refactor this to use try-with-resources. // Handling RC decrements manually for now to minimize conflicts. - rcOmSnapshot = omSnapshotManager.checkForSnapshot( + rcOmSnapshot = omSnapshotManager.getSnapshot( snapInfo.getVolumeName(), snapInfo.getBucketName(), - getSnapshotPrefix(snapInfo.getName()), - true); - OmSnapshot omSnapshot = (OmSnapshot) rcOmSnapshot.get(); + snapInfo.getName()); + OmSnapshot omSnapshot = rcOmSnapshot.get(); Table snapshotDeletedTable = omSnapshot.getMetadataManager().getDeletedTable(); @@ -226,12 +220,11 @@ public BackgroundTaskResult call() throws InterruptedException { // Split RepeatedOmKeyInfo and update current snapshot deletedKeyTable // and next snapshot deletedKeyTable. if (previousSnapshot != null) { - rcOmPreviousSnapshot = omSnapshotManager.checkForSnapshot( + rcOmPreviousSnapshot = omSnapshotManager.getSnapshot( previousSnapshot.getVolumeName(), previousSnapshot.getBucketName(), - getSnapshotPrefix(previousSnapshot.getName()), - true); - omPreviousSnapshot = (OmSnapshot) rcOmPreviousSnapshot.get(); + previousSnapshot.getName()); + omPreviousSnapshot = rcOmPreviousSnapshot.get(); previousKeyTable = omPreviousSnapshot .getMetadataManager().getKeyTable(bucketInfo.getBucketLayout()); diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/SnapshotDirectoryCleaningService.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/SnapshotDirectoryCleaningService.java index 9a60f6303861..fe0f6e111ed3 100644 --- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/SnapshotDirectoryCleaningService.java +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/SnapshotDirectoryCleaningService.java @@ -28,7 +28,6 @@ import org.apache.hadoop.hdds.utils.db.Table; import org.apache.hadoop.hdds.utils.db.TableIterator; import org.apache.hadoop.ozone.common.BlockGroup; -import org.apache.hadoop.ozone.om.IOmMetadataReader; import org.apache.hadoop.ozone.om.OMMetadataManager; import org.apache.hadoop.ozone.om.OmMetadataManagerImpl; import org.apache.hadoop.ozone.om.OmSnapshot; @@ -44,7 +43,6 @@ import org.apache.hadoop.ozone.om.ratis.OzoneManagerRatisServer; import org.apache.hadoop.ozone.om.request.file.OMFileRequest; import org.apache.hadoop.ozone.om.snapshot.ReferenceCounted; -import org.apache.hadoop.ozone.om.snapshot.SnapshotCache; import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest; import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.SetSnapshotPropertyRequest; import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.SnapshotSize; @@ -63,7 +61,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; -import static org.apache.hadoop.ozone.om.OmSnapshotManager.getSnapshotPrefix; import static org.apache.hadoop.ozone.om.helpers.SnapshotInfo.SnapshotStatus.SNAPSHOT_ACTIVE; import static org.apache.hadoop.ozone.om.request.file.OMFileRequest.getDirectoryInfo; import static org.apache.hadoop.ozone.om.snapshot.SnapshotUtils.getOzonePathKeyForFso; @@ -158,10 +155,8 @@ public BackgroundTaskResult call() { continue; } - ReferenceCounted - rcPrevOmSnapshot = null; - ReferenceCounted - rcPrevToPrevOmSnapshot = null; + ReferenceCounted rcPrevOmSnapshot = null; + ReferenceCounted rcPrevToPrevOmSnapshot = null; try { long volumeId = metadataManager .getVolumeId(currSnapInfo.getVolumeName()); @@ -189,12 +184,11 @@ public BackgroundTaskResult call() { Table prevRenamedTable = null; if (previousSnapshot != null) { - rcPrevOmSnapshot = omSnapshotManager.checkForSnapshot( + rcPrevOmSnapshot = omSnapshotManager.getActiveSnapshot( previousSnapshot.getVolumeName(), previousSnapshot.getBucketName(), - getSnapshotPrefix(previousSnapshot.getName()), false); - OmSnapshot omPreviousSnapshot = (OmSnapshot) - rcPrevOmSnapshot.get(); + previousSnapshot.getName()); + OmSnapshot omPreviousSnapshot = rcPrevOmSnapshot.get(); previousKeyTable = omPreviousSnapshot.getMetadataManager() .getKeyTable(bucketInfo.getBucketLayout()); @@ -206,12 +200,11 @@ public BackgroundTaskResult call() { Table previousToPrevKeyTable = null; if (previousToPrevSnapshot != null) { - rcPrevToPrevOmSnapshot = omSnapshotManager.checkForSnapshot( + rcPrevToPrevOmSnapshot = omSnapshotManager.getActiveSnapshot( previousToPrevSnapshot.getVolumeName(), previousToPrevSnapshot.getBucketName(), - getSnapshotPrefix(previousToPrevSnapshot.getName()), false); - OmSnapshot omPreviousToPrevSnapshot = (OmSnapshot) - rcPrevToPrevOmSnapshot.get(); + previousToPrevSnapshot.getName()); + OmSnapshot omPreviousToPrevSnapshot = rcPrevToPrevOmSnapshot.get(); previousToPrevKeyTable = omPreviousToPrevSnapshot .getMetadataManager() @@ -220,14 +213,13 @@ public BackgroundTaskResult call() { String dbBucketKeyForDir = getOzonePathKeyForFso(metadataManager, currSnapInfo.getVolumeName(), currSnapInfo.getBucketName()); - try (ReferenceCounted - rcCurrOmSnapshot = omSnapshotManager.checkForSnapshot( + try (ReferenceCounted + rcCurrOmSnapshot = omSnapshotManager.getActiveSnapshot( currSnapInfo.getVolumeName(), currSnapInfo.getBucketName(), - getSnapshotPrefix(currSnapInfo.getName()), - false)) { + currSnapInfo.getName())) { - OmSnapshot currOmSnapshot = (OmSnapshot) rcCurrOmSnapshot.get(); + OmSnapshot currOmSnapshot = rcCurrOmSnapshot.get(); Table snapDeletedDirTable = currOmSnapshot.getMetadataManager().getDeletedDirTable(); diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/ReferenceCounted.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/ReferenceCounted.java index 808a5ed4c192..97e19eb969d8 100644 --- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/ReferenceCounted.java +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/ReferenceCounted.java @@ -25,7 +25,7 @@ /** * Add reference counter to an object instance. */ -public class ReferenceCounted +public class ReferenceCounted implements AutoCloseable { /** @@ -51,10 +51,10 @@ public class ReferenceCounted /** * Parent instance whose callback will be triggered upon this RC closure. */ - private final U parentWithCallback; + private final ReferenceCountedCallback parentWithCallback; public ReferenceCounted(T obj, boolean disableCounter, - U parentWithCallback) { + ReferenceCountedCallback parentWithCallback) { // A param to allow disabling ref counting to reduce active DB // access penalties due to AtomicLong operations. this.obj = obj; @@ -126,7 +126,9 @@ public long decrementRefCount() { Preconditions.checkState(newValTotal >= 0L, "Total reference count underflow"); } - + if (refCount.get() == 0) { + this.parentWithCallback.callback(this); + } return refCount.get(); } diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/ReferenceCountedCallback.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/ReferenceCountedCallback.java new file mode 100644 index 000000000000..d63f5783b808 --- /dev/null +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/ReferenceCountedCallback.java @@ -0,0 +1,25 @@ +/* + * 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.ozone.om.snapshot; + +/** + * Callback interface for ReferenceCounted. + */ +public interface ReferenceCountedCallback { + void callback(ReferenceCounted referenceCounted); +} diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/SnapshotCache.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/SnapshotCache.java index 226acbb7dd1b..035fc80d3468 100644 --- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/SnapshotCache.java +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/SnapshotCache.java @@ -19,52 +19,65 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.cache.CacheLoader; -import org.apache.hadoop.ozone.om.IOmMetadataReader; +import org.apache.hadoop.ozone.om.OMMetrics; +import org.apache.hadoop.hdds.utils.Scheduler; import org.apache.hadoop.ozone.om.OmSnapshot; -import org.apache.hadoop.ozone.om.OmSnapshotManager; import org.apache.hadoop.ozone.om.exceptions.OMException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; -import java.util.Iterator; -import java.util.Map; +import java.util.Set; +import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.FILE_NOT_FOUND; -import static org.apache.hadoop.ozone.om.helpers.SnapshotInfo.SnapshotStatus.SNAPSHOT_ACTIVE; /** * Thread-safe custom unbounded LRU cache to manage open snapshot DB instances. */ -public class SnapshotCache { +public class SnapshotCache implements ReferenceCountedCallback, AutoCloseable { static final Logger LOG = LoggerFactory.getLogger(SnapshotCache.class); // Snapshot cache internal hash map. - // Key: DB snapshot table key + // Key: SnapshotId // Value: OmSnapshot instance, each holds a DB instance handle inside // TODO: [SNAPSHOT] Consider wrapping SoftReference<> around IOmMetadataReader - private final ConcurrentHashMap> dbMap; + private final ConcurrentHashMap> dbMap; + + private final CacheLoader cacheLoader; - private final OmSnapshotManager omSnapshotManager; - private final CacheLoader cacheLoader; // Soft-limit of the total number of snapshot DB instances allowed to be // opened on the OM. private final int cacheSizeLimit; + private final Set pendingEvictionQueue; + private final Scheduler scheduler; + private static final String SNAPSHOT_CACHE_CLEANUP_SERVICE = + "SnapshotCacheCleanupService"; + + private final OMMetrics omMetrics; - public SnapshotCache( - OmSnapshotManager omSnapshotManager, - CacheLoader cacheLoader, - int cacheSizeLimit) { + public SnapshotCache(CacheLoader cacheLoader, int cacheSizeLimit, OMMetrics omMetrics, + long cleanupInterval) { this.dbMap = new ConcurrentHashMap<>(); - this.omSnapshotManager = omSnapshotManager; this.cacheLoader = cacheLoader; this.cacheSizeLimit = cacheSizeLimit; + this.omMetrics = omMetrics; + this.pendingEvictionQueue = ConcurrentHashMap.newKeySet(); + if (cleanupInterval > 0) { + this.scheduler = new Scheduler(SNAPSHOT_CACHE_CLEANUP_SERVICE, + true, 1); + this.scheduler.scheduleWithFixedDelay(this::cleanup, cleanupInterval, + cleanupInterval, TimeUnit.MILLISECONDS); + } else { + this.scheduler = null; + } } @VisibleForTesting - ConcurrentHashMap> getDbMap() { + ConcurrentHashMap> getDbMap() { return dbMap; } @@ -77,18 +90,19 @@ public int size() { /** * Immediately invalidate an entry. - * @param key DB snapshot table key + * @param key SnapshotId */ - public void invalidate(String key) throws IOException { + public void invalidate(UUID key) { dbMap.compute(key, (k, v) -> { if (v == null) { - LOG.warn("Key: '{}' does not exist in cache.", k); + LOG.warn("SnapshotId: '{}' does not exist in snapshot cache.", k); } else { try { - ((OmSnapshot) v.get()).close(); + v.get().close(); } catch (IOException e) { - throw new IllegalStateException("Failed to close snapshot: " + key, e); + throw new IllegalStateException("Failed to close snapshotId: " + key, e); } + omMetrics.decNumSnapshotCacheSize(); } return null; }); @@ -98,19 +112,16 @@ public void invalidate(String key) throws IOException { * Immediately invalidate all entries and close their DB instances in cache. */ public void invalidateAll() { - Iterator>> - it = dbMap.entrySet().iterator(); + for (UUID key : dbMap.keySet()) { + invalidate(key); + } + } - while (it.hasNext()) { - Map.Entry> entry = it.next(); - OmSnapshot omSnapshot = (OmSnapshot) entry.getValue().get(); - try { - // TODO: If wrapped with SoftReference<>, omSnapshot could be null? - omSnapshot.close(); - } catch (IOException e) { - throw new IllegalStateException("Failed to close snapshot", e); - } - it.remove(); + @Override + public void close() { + invalidateAll(); + if (this.scheduler != null) { + this.scheduler.close(); } } @@ -120,31 +131,31 @@ public void invalidateAll() { */ public enum Reason { FS_API_READ, - SNAPDIFF_READ, + SNAP_DIFF_READ, DEEP_CLEAN_WRITE, GARBAGE_COLLECTION_WRITE } - public ReferenceCounted get(String key) throws IOException { - return get(key, false); - } - /** * Get or load OmSnapshot. Shall be close()d after use. * TODO: [SNAPSHOT] Can add reason enum to param list later. - * @param key snapshot table key + * @param key SnapshotId * @return an OmSnapshot instance, or null on error */ - public ReferenceCounted get(String key, boolean skipActiveCheck) - throws IOException { + public ReferenceCounted get(UUID key) throws IOException { + // Warn if actual cache size exceeds the soft limit already. + if (size() > cacheSizeLimit) { + LOG.warn("Snapshot cache size ({}) exceeds configured soft-limit ({}).", + size(), cacheSizeLimit); + } // Atomic operation to initialize the OmSnapshot instance (once) if the key // does not exist, and increment the reference count on the instance. - ReferenceCounted rcOmSnapshot = + ReferenceCounted rcOmSnapshot = dbMap.compute(key, (k, v) -> { if (v == null) { - LOG.info("Loading snapshot. Table key: {}", k); + LOG.info("Loading SnapshotId: '{}'", k); try { - v = new ReferenceCounted<>(cacheLoader.load(k), false, this); + v = new ReferenceCounted<>(cacheLoader.load(key), false, this); } catch (OMException omEx) { // Return null if the snapshot is no longer active if (!omEx.getResult().equals(FILE_NOT_FOUND)) { @@ -157,6 +168,7 @@ public ReferenceCounted get(String key, boolea // Unexpected and unknown exception thrown from CacheLoader#load throw new IllegalStateException(ex); } + omMetrics.incNumSnapshotCacheSize(); } if (v != null) { // When RC OmSnapshot is successfully loaded @@ -164,101 +176,74 @@ public ReferenceCounted get(String key, boolea } return v; }); - if (rcOmSnapshot == null) { // The only exception that would fall through the loader logic above // is OMException with FILE_NOT_FOUND. - throw new OMException("Snapshot table key '" + key + "' not found, " - + "or the snapshot is no longer active", + throw new OMException("SnapshotId: '" + key + "' not found, or the snapshot is no longer active.", OMException.ResultCodes.FILE_NOT_FOUND); } - - // If the snapshot is already loaded in cache, the check inside the loader - // above is ignored. But we would still want to reject all get()s except - // when called from SDT (and some) if the snapshot is not active anymore. - if (!skipActiveCheck && !omSnapshotManager.isSnapshotStatus(key, SNAPSHOT_ACTIVE)) { - // Ref count was incremented. Need to decrement on exception here. - rcOmSnapshot.decrementRefCount(); - throw new OMException("Unable to load snapshot. " + - "Snapshot with table key '" + key + "' is no longer active", - FILE_NOT_FOUND); - } - - // Check if any entries can be cleaned up. - // At this point, cache size might temporarily exceed cacheSizeLimit - // even if there are entries that can be evicted, which is fine since it - // is a soft limit. - cleanup(); - return rcOmSnapshot; } /** * Release the reference count on the OmSnapshot instance. - * @param key snapshot table key + * @param key SnapshotId */ - public void release(String key) { - dbMap.compute(key, (k, v) -> { - if (v == null) { - throw new IllegalArgumentException("Key '" + key + "' does not exist in cache."); - } else { - v.decrementRefCount(); - } - return v; - }); - - // The cache size might have already exceeded the soft limit - // Thus triggering cleanup() to check and evict if applicable - cleanup(); + public void release(UUID key) { + ReferenceCounted val = dbMap.get(key); + if (val == null) { + throw new IllegalArgumentException("Key '" + key + "' does not " + + "exist in cache."); + } + val.decrementRefCount(); } - /** - * Alternatively, can release with OmSnapshot instance directly. - * @param omSnapshot OmSnapshot - */ - public void release(OmSnapshot omSnapshot) { - final String snapshotTableKey = omSnapshot.getSnapshotTableKey(); - release(snapshotTableKey); - } /** - * Wrapper for cleanupInternal() that is synchronized to prevent multiple - * threads from interleaving into the cleanup method. + * If cache size exceeds soft limit, attempt to clean up and close the + instances that has zero reference count. */ - private synchronized void cleanup() { + @VisibleForTesting + void cleanup() { if (dbMap.size() > cacheSizeLimit) { - cleanupInternal(); + for (UUID evictionKey : pendingEvictionQueue) { + dbMap.compute(evictionKey, (k, v) -> { + pendingEvictionQueue.remove(k); + if (v == null) { + throw new IllegalStateException("SnapshotId '" + k + "' does not exist in cache. The RocksDB " + + "instance of the Snapshot may not be closed properly."); + } + + if (v.getTotalRefCount() > 0) { + LOG.debug("SnapshotId {} is still being referenced ({}), skipping its clean up.", k, v.getTotalRefCount()); + return v; + } else { + LOG.debug("Closing SnapshotId {}. It is not being referenced anymore.", k); + // Close the instance, which also closes its DB handle. + try { + v.get().close(); + } catch (IOException ex) { + throw new IllegalStateException("Error while closing snapshot DB.", ex); + } + omMetrics.decNumSnapshotCacheSize(); + return null; + } + }); + } } } /** - * If cache size exceeds soft limit, attempt to clean up and close the - * instances that has zero reference count. - * TODO: [SNAPSHOT] Add new ozone debug CLI command to trigger this directly. + * Callback method used to enqueue or dequeue ReferenceCounted from + * pendingEvictionList. + * @param referenceCounted ReferenceCounted object */ - private void cleanupInternal() { - for (Map.Entry> entry : dbMap.entrySet()) { - dbMap.compute(entry.getKey(), (k, v) -> { - if (v == null) { - throw new IllegalStateException("Key '" + k + "' does not exist in cache. The RocksDB " + - "instance of the Snapshot may not be closed properly."); - } - - if (v.getTotalRefCount() > 0) { - LOG.debug("Snapshot {} is still being referenced ({}), skipping its clean up", - k, v.getTotalRefCount()); - return v; - } else { - LOG.debug("Closing Snapshot {}. It is not being referenced anymore.", k); - // Close the instance, which also closes its DB handle. - try { - ((OmSnapshot) v.get()).close(); - } catch (IOException ex) { - throw new IllegalStateException("Error while closing snapshot DB", ex); - } - return null; - } - }); + @Override + public void callback(ReferenceCounted referenceCounted) { + if (referenceCounted.getTotalRefCount() == 0L) { + // Reference count reaches zero, add to pendingEvictionList + pendingEvictionQueue.add(((OmSnapshot) referenceCounted.get()) + .getSnapshotID()); } } } diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/SnapshotDiffManager.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/SnapshotDiffManager.java index 2c0a564306b6..eb9fbc5ca2e6 100644 --- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/SnapshotDiffManager.java +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/SnapshotDiffManager.java @@ -36,7 +36,6 @@ import org.apache.hadoop.hdfs.protocol.SnapshotDiffReport.DiffReportEntry; import org.apache.hadoop.ozone.OFSPath; import org.apache.hadoop.ozone.OzoneConsts; -import org.apache.hadoop.ozone.om.IOmMetadataReader; import org.apache.hadoop.ozone.om.OMConfigKeys; import org.apache.hadoop.ozone.om.OMMetadataManager; import org.apache.hadoop.ozone.om.OmMetadataManagerImpl; @@ -56,7 +55,7 @@ import org.apache.hadoop.ozone.snapshot.SnapshotDiffResponse.JobStatus; import org.apache.hadoop.util.ClosableIterator; import org.apache.logging.log4j.util.Strings; -import org.apache.ozone.rocksdb.util.ManagedSstFileReader; +import org.apache.ozone.rocksdb.util.SstFileSetReader; import org.apache.ozone.rocksdb.util.RdbUtil; import org.apache.ozone.rocksdiff.DifferSnapshotInfo; import org.apache.ozone.rocksdiff.RocksDBCheckpointDiffer; @@ -114,7 +113,6 @@ import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_SNAPSHOT_DIFF_DISABLE_NATIVE_LIBS_DEFAULT; import static org.apache.hadoop.ozone.om.OmMetadataManagerImpl.DIRECTORY_TABLE; import static org.apache.hadoop.ozone.om.OmSnapshotManager.DELIMITER; -import static org.apache.hadoop.ozone.om.helpers.SnapshotInfo.getTableKey; import static org.apache.hadoop.ozone.om.snapshot.SnapshotUtils.checkSnapshotActive; import static org.apache.hadoop.ozone.om.snapshot.SnapshotUtils.dropColumnFamilyHandle; import static org.apache.hadoop.ozone.om.snapshot.SnapshotUtils.getColumnFamilyToKeyPrefixMap; @@ -150,7 +148,6 @@ public class SnapshotDiffManager implements AutoCloseable { private final ManagedRocksDB db; private final RocksDBCheckpointDiffer differ; private final OzoneManager ozoneManager; - private final SnapshotCache snapshotCache; private final CodecRegistry codecRegistry; private final ManagedColumnFamilyOptions familyOptions; // TODO: [SNAPSHOT] Use different wait time based of job status. @@ -199,7 +196,6 @@ public class SnapshotDiffManager implements AutoCloseable { public SnapshotDiffManager(ManagedRocksDB db, RocksDBCheckpointDiffer differ, OzoneManager ozoneManager, - SnapshotCache snapshotCache, ColumnFamilyHandle snapDiffJobCfh, ColumnFamilyHandle snapDiffReportCfh, ManagedColumnFamilyOptions familyOptions, @@ -207,7 +203,6 @@ public SnapshotDiffManager(ManagedRocksDB db, this.db = db; this.differ = differ; this.ozoneManager = ozoneManager; - this.snapshotCache = snapshotCache; this.familyOptions = familyOptions; this.codecRegistry = codecRegistry; this.defaultWaitTime = ozoneManager.getConfiguration().getTimeDuration( @@ -832,8 +827,8 @@ void generateSnapshotDiffReport(final String jobKey, // job by RocksDBCheckpointDiffer#pruneOlderSnapshotsWithCompactionHistory. Path path = Paths.get(sstBackupDirForSnapDiffJobs + "/" + jobId); - ReferenceCounted rcFromSnapshot = null; - ReferenceCounted rcToSnapshot = null; + ReferenceCounted rcFromSnapshot = null; + ReferenceCounted rcToSnapshot = null; try { if (!areDiffJobAndSnapshotsActive(volumeName, bucketName, @@ -841,14 +836,15 @@ void generateSnapshotDiffReport(final String jobKey, return; } - String fsKey = getTableKey(volumeName, bucketName, fromSnapshotName); - String tsKey = getTableKey(volumeName, bucketName, toSnapshotName); + rcFromSnapshot = + ozoneManager.getOmSnapshotManager() + .getActiveSnapshot(volumeName, bucketName, fromSnapshotName); + rcToSnapshot = + ozoneManager.getOmSnapshotManager() + .getActiveSnapshot(volumeName, bucketName, toSnapshotName); - rcFromSnapshot = snapshotCache.get(fsKey); - rcToSnapshot = snapshotCache.get(tsKey); - - OmSnapshot fromSnapshot = (OmSnapshot) rcFromSnapshot.get(); - OmSnapshot toSnapshot = (OmSnapshot) rcToSnapshot.get(); + OmSnapshot fromSnapshot = rcFromSnapshot.get(); + OmSnapshot toSnapshot = rcToSnapshot.get(); SnapshotInfo fsInfo = getSnapshotInfo(ozoneManager, volumeName, bucketName, fromSnapshotName); SnapshotInfo tsInfo = getSnapshotInfo(ozoneManager, @@ -1083,7 +1079,7 @@ void addToObjectIdMap(Table fsTable, String tablePrefix = getTablePrefix(tablePrefixes, fsTable.getName()); boolean isDirectoryTable = fsTable.getName().equals(DIRECTORY_TABLE); - ManagedSstFileReader sstFileReader = new ManagedSstFileReader(deltaFiles); + SstFileSetReader sstFileReader = new SstFileSetReader(deltaFiles); validateEstimatedKeyChangesAreInLimits(sstFileReader); String sstFileReaderLowerBound = tablePrefix; String sstFileReaderUpperBound = null; @@ -1204,7 +1200,7 @@ Set getDeltaFiles(OmSnapshot fromSnapshot, } private void validateEstimatedKeyChangesAreInLimits( - ManagedSstFileReader sstFileReader + SstFileSetReader sstFileReader ) throws RocksDBException, IOException { if (sstFileReader.getEstimatedTotalKeys() > maxAllowedKeyChangesForASnapDiff) { diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestChunkStreams.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestChunkStreams.java index dbcf9f6ea4e4..f2595782255e 100644 --- a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestChunkStreams.java +++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestChunkStreams.java @@ -17,12 +17,15 @@ package org.apache.hadoop.ozone.om; import org.apache.commons.lang3.RandomStringUtils; +import org.apache.hadoop.hdds.conf.OzoneConfiguration; +import org.apache.hadoop.hdds.scm.OzoneClientConfig; import org.apache.hadoop.hdds.scm.storage.BlockInputStream; import org.apache.hadoop.ozone.client.io.KeyInputStream; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; import java.io.ByteArrayInputStream; +import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -34,6 +37,8 @@ */ public class TestChunkStreams { + private OzoneConfiguration conf = new OzoneConfiguration(); + @Test public void testReadGroupInputStream() throws Exception { String dataString = RandomStringUtils.randomAscii(500); @@ -77,7 +82,7 @@ public void testErrorReadGroupInputStream() throws Exception { } @NotNull - private List createInputStreams(String dataString) { + private List createInputStreams(String dataString) throws IOException { byte[] buf = dataString.getBytes(UTF_8); List streams = new ArrayList<>(); int offset = 0; @@ -89,8 +94,11 @@ private List createInputStreams(String dataString) { return streams; } - private BlockInputStream createStream(byte[] buf, int offset) { - return new BlockInputStream(null, 100, null, null, true, null) { + private BlockInputStream createStream(byte[] buf, int offset) throws IOException { + OzoneClientConfig clientConfig = conf.getObject(OzoneClientConfig.class); + clientConfig.setChecksumVerify(true); + return new BlockInputStream(null, 100, null, null, null, + clientConfig) { private long pos; private final ByteArrayInputStream in = new ByteArrayInputStream(buf, offset, 100); diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestOmSnapshotManager.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestOmSnapshotManager.java index 462c2a3b889c..9c4a929d68b0 100644 --- a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestOmSnapshotManager.java +++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestOmSnapshotManager.java @@ -65,7 +65,6 @@ import static org.apache.hadoop.ozone.om.OmMetadataManagerImpl.VOLUME_TABLE; import static org.apache.hadoop.ozone.om.OmSnapshotManager.OM_HARDLINK_FILE; import static org.apache.hadoop.ozone.om.snapshot.OmSnapshotUtils.getINode; -import static org.apache.hadoop.ozone.om.OmSnapshotManager.getSnapshotPrefix; import static org.apache.hadoop.ozone.om.snapshot.OmSnapshotUtils.truncateFileName; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.timeout; @@ -168,18 +167,25 @@ public void testCloseOnEviction() throws IOException { SnapshotInfo first = createSnapshotInfo(volumeName, bucketName); SnapshotInfo second = createSnapshotInfo(volumeName, bucketName); + first.setGlobalPreviousSnapshotId(null); + first.setPathPreviousSnapshotId(null); + second.setGlobalPreviousSnapshotId(first.getSnapshotId()); + second.setPathPreviousSnapshotId(first.getSnapshotId()); + when(snapshotInfoTable.get(first.getTableKey())).thenReturn(first); when(snapshotInfoTable.get(second.getTableKey())).thenReturn(second); + ((OmMetadataManagerImpl) om.getMetadataManager()).getSnapshotChainManager().addSnapshot(first); + ((OmMetadataManagerImpl) om.getMetadataManager()).getSnapshotChainManager().addSnapshot(second); // create the first snapshot checkpoint OmSnapshotManager.createOmSnapshotCheckpoint(om.getMetadataManager(), first); // retrieve it and setup store mock OmSnapshotManager omSnapshotManager = om.getOmSnapshotManager(); - OmSnapshot firstSnapshot = (OmSnapshot) omSnapshotManager - .checkForSnapshot(first.getVolumeName(), - first.getBucketName(), getSnapshotPrefix(first.getName()), false).get(); + OmSnapshot firstSnapshot = omSnapshotManager + .getActiveSnapshot(first.getVolumeName(), first.getBucketName(), first.getName()) + .get(); DBStore firstSnapshotStore = mock(DBStore.class); HddsWhiteboxTestUtils.setInternalState( firstSnapshot.getMetadataManager(), "store", firstSnapshotStore); @@ -193,13 +199,12 @@ public void testCloseOnEviction() throws IOException { // read in second snapshot to evict first omSnapshotManager - .checkForSnapshot(second.getVolumeName(), - second.getBucketName(), getSnapshotPrefix(second.getName()), false); + .getActiveSnapshot(second.getVolumeName(), second.getBucketName(), second.getName()); // As a workaround, invalidate all cache entries in order to trigger // instances close in this test case, since JVM GC most likely would not // have triggered and closed the instances yet at this point. - omSnapshotManager.getSnapshotCache().invalidateAll(); + omSnapshotManager.invalidateCache(); // confirm store was closed verify(firstSnapshotStore, timeout(3000).times(1)).close(); diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestSstFilteringService.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestSstFilteringService.java index 2009f7e5da5a..678efabc3180 100644 --- a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestSstFilteringService.java +++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestSstFilteringService.java @@ -35,7 +35,6 @@ import org.apache.hadoop.ozone.om.protocol.OzoneManagerProtocol; import org.apache.hadoop.ozone.om.request.OMRequestTestUtils; import org.apache.hadoop.ozone.om.snapshot.ReferenceCounted; -import org.apache.hadoop.ozone.om.snapshot.SnapshotCache; import org.apache.hadoop.security.authentication.client.AuthenticationException; import org.apache.ratis.util.ExitUtils; import org.awaitility.core.ConditionTimeoutException; @@ -475,11 +474,12 @@ private Set getKeysFromSnapshot(String volume, String snapshot) throws IOException { SnapshotInfo snapshotInfo = om.getMetadataManager().getSnapshotInfoTable() .get(SnapshotInfo.getTableKey(volume, bucket, snapshot)); - try (ReferenceCounted - snapshotMetadataReader = om.getOmSnapshotManager() - .getSnapshotCache() - .get(snapshotInfo.getTableKey())) { - OmSnapshot omSnapshot = (OmSnapshot) snapshotMetadataReader.get(); + try (ReferenceCounted snapshotMetadataReader = + om.getOmSnapshotManager().getActiveSnapshot( + snapshotInfo.getVolumeName(), + snapshotInfo.getBucketName(), + snapshotInfo.getName())) { + OmSnapshot omSnapshot = snapshotMetadataReader.get(); return getKeysFromDb(omSnapshot.getMetadataManager(), volume, bucket); } } diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeyPurgeRequestAndResponse.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeyPurgeRequestAndResponse.java index cb0b03ff6166..49ea32138864 100644 --- a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeyPurgeRequestAndResponse.java +++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeyPurgeRequestAndResponse.java @@ -23,7 +23,6 @@ import java.util.List; import java.util.UUID; -import org.apache.hadoop.ozone.om.IOmMetadataReader; import org.apache.hadoop.ozone.om.OmSnapshot; import org.apache.hadoop.ozone.om.helpers.SnapshotInfo; import org.apache.hadoop.ozone.om.request.OMRequestTestUtils; @@ -31,7 +30,6 @@ import org.apache.hadoop.ozone.om.request.snapshot.TestOMSnapshotCreateRequest; import org.apache.hadoop.ozone.om.response.snapshot.OMSnapshotCreateResponse; import org.apache.hadoop.ozone.om.snapshot.ReferenceCounted; -import org.apache.hadoop.ozone.om.snapshot.SnapshotCache; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -45,7 +43,6 @@ import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.Type; import org.apache.hadoop.hdds.utils.db.BatchOperation; -import static org.apache.hadoop.ozone.om.OmSnapshotManager.getSnapshotPrefix; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; @@ -218,13 +215,12 @@ public void testKeyPurgeInSnapshot() throws Exception { .setName("snap1") .build(); - ReferenceCounted rcOmSnapshot = - ozoneManager.getOmSnapshotManager().checkForSnapshot( + ReferenceCounted rcOmSnapshot = + ozoneManager.getOmSnapshotManager().getSnapshot( fromSnapshotInfo.getVolumeName(), fromSnapshotInfo.getBucketName(), - getSnapshotPrefix(fromSnapshotInfo.getName()), - true); - OmSnapshot omSnapshot = (OmSnapshot) rcOmSnapshot.get(); + fromSnapshotInfo.getName()); + OmSnapshot omSnapshot = rcOmSnapshot.get(); // The keys should be present in the snapshot's deletedTable for (String deletedKey : deletedKeyNames) { diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeyRequest.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeyRequest.java index ce83d96646d6..178134e6941e 100644 --- a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeyRequest.java +++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeyRequest.java @@ -41,7 +41,6 @@ import org.apache.hadoop.ozone.om.helpers.OmKeyInfo; import org.apache.hadoop.ozone.om.request.OMClientRequest; import org.apache.hadoop.ozone.om.snapshot.ReferenceCounted; -import org.apache.hadoop.ozone.om.snapshot.SnapshotCache; import org.apache.hadoop.ozone.om.upgrade.OMLayoutVersionManager; import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.KeyArgs; import org.apache.hadoop.ozone.security.acl.OzoneNativeAuthorizer; @@ -169,7 +168,7 @@ public void setup() throws Exception { when(ozoneManager.getAccessAuthorizer()) .thenReturn(new OzoneNativeAuthorizer()); - ReferenceCounted rcOmMetadataReader = + ReferenceCounted rcOmMetadataReader = mock(ReferenceCounted.class); when(ozoneManager.getOmMetadataReader()).thenReturn(rcOmMetadataReader); // Init OmMetadataReader to let the test pass diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/s3/multipart/TestS3MultipartRequest.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/s3/multipart/TestS3MultipartRequest.java index d952bdf090d0..831d78c282c7 100644 --- a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/s3/multipart/TestS3MultipartRequest.java +++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/s3/multipart/TestS3MultipartRequest.java @@ -41,7 +41,6 @@ import org.apache.hadoop.ozone.om.OMMetadataManager; import org.apache.hadoop.ozone.om.OmMetadataReader; import org.apache.hadoop.ozone.om.IOmMetadataReader; -import org.apache.hadoop.ozone.om.snapshot.SnapshotCache; import org.apache.hadoop.ozone.om.snapshot.ReferenceCounted; import org.apache.hadoop.ozone.om.OMMetrics; import org.apache.hadoop.ozone.om.OmMetadataManagerImpl; @@ -82,7 +81,7 @@ public void setup() throws Exception { when(ozoneManager.getMetrics()).thenReturn(omMetrics); when(ozoneManager.getMetadataManager()).thenReturn(omMetadataManager); auditLogger = mock(AuditLogger.class); - ReferenceCounted rcOmMetadataReader = + ReferenceCounted rcOmMetadataReader = mock(ReferenceCounted.class); when(ozoneManager.getOmMetadataReader()).thenReturn(rcOmMetadataReader); // Init OmMetadataReader to let the test pass diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotDeleteRequest.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotDeleteRequest.java index d79e7340c93c..ec20e79a723f 100644 --- a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotDeleteRequest.java +++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotDeleteRequest.java @@ -33,7 +33,6 @@ import org.apache.hadoop.ozone.om.helpers.SnapshotInfo; import org.apache.hadoop.ozone.om.request.OMRequestTestUtils; import org.apache.hadoop.ozone.om.response.OMClientResponse; -import org.apache.hadoop.ozone.om.snapshot.SnapshotCache; import org.apache.hadoop.ozone.om.upgrade.OMLayoutVersionManager; import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest; import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMResponse; @@ -108,8 +107,6 @@ public void setup() throws Exception { Mockito.doNothing().when(auditLogger).logWrite(any(AuditMessage.class)); OmSnapshotManager omSnapshotManager = mock(OmSnapshotManager.class); - when(omSnapshotManager.getSnapshotCache()) - .thenReturn(mock(SnapshotCache.class)); when(ozoneManager.getOmSnapshotManager()).thenReturn(omSnapshotManager); volumeName = UUID.randomUUID().toString(); diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotPurgeRequestAndResponse.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotPurgeRequestAndResponse.java index b666caf21d95..760969daa8fb 100644 --- a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotPurgeRequestAndResponse.java +++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotPurgeRequestAndResponse.java @@ -38,7 +38,6 @@ import org.apache.hadoop.ozone.om.response.snapshot.OMSnapshotCreateResponse; import org.apache.hadoop.ozone.om.response.snapshot.OMSnapshotPurgeResponse; import org.apache.hadoop.ozone.om.snapshot.ReferenceCounted; -import org.apache.hadoop.ozone.om.snapshot.SnapshotCache; import org.apache.hadoop.ozone.om.upgrade.OMLayoutVersionManager; import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest; import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.SnapshotPurgeRequest; @@ -117,7 +116,7 @@ void setup(@TempDir File testDir) throws Exception { when(ozoneManager.isAdmin(any())).thenReturn(true); when(ozoneManager.isFilesystemSnapshotEnabled()).thenReturn(true); - ReferenceCounted rcOmMetadataReader = + ReferenceCounted rcOmMetadataReader = Mockito.mock(ReferenceCounted.class); when(ozoneManager.getOmMetadataReader()).thenReturn(rcOmMetadataReader); omSnapshotManager = new OmSnapshotManager(ozoneManager); diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/service/TestKeyDeletingService.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/service/TestKeyDeletingService.java index 1131f81c5773..79a06a78bcab 100644 --- a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/service/TestKeyDeletingService.java +++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/service/TestKeyDeletingService.java @@ -36,7 +36,6 @@ import org.apache.hadoop.hdds.client.RatisReplicationConfig; import org.apache.hadoop.hdds.utils.db.Table; import org.apache.hadoop.hdds.utils.db.TableIterator; -import org.apache.hadoop.ozone.om.IOmMetadataReader; import org.apache.hadoop.ozone.om.KeyManager; import org.apache.hadoop.ozone.om.OMMetadataManager; import org.apache.hadoop.ozone.om.OmSnapshot; @@ -52,7 +51,6 @@ import org.apache.hadoop.ozone.om.helpers.SnapshotInfo; import org.apache.hadoop.ozone.om.request.OMRequestTestUtils; import org.apache.hadoop.ozone.om.snapshot.ReferenceCounted; -import org.apache.hadoop.ozone.om.snapshot.SnapshotCache; import org.apache.ratis.util.ExitUtils; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.io.TempDir; @@ -79,7 +77,6 @@ import static org.apache.hadoop.hdds.protocol.proto.HddsProtos.ReplicationFactor.THREE; import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_BLOCK_DELETING_SERVICE_INTERVAL; import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_SNAPSHOT_DELETING_SERVICE_INTERVAL; -import static org.apache.hadoop.ozone.om.OmSnapshotManager.getSnapshotPrefix; import static org.junit.jupiter.api.Assertions.fail; import org.junit.jupiter.api.AfterEach; @@ -546,10 +543,9 @@ public void testSnapshotDeepClean() throws Exception { keyDeletingService.resume(); - try (ReferenceCounted rcOmSnapshot = - om.getOmSnapshotManager().checkForSnapshot( - volumeName, bucketName, getSnapshotPrefix("snap3"), true)) { - OmSnapshot snap3 = (OmSnapshot) rcOmSnapshot.get(); + try (ReferenceCounted rcOmSnapshot = + om.getOmSnapshotManager().getSnapshot(volumeName, bucketName, "snap3", true)) { + OmSnapshot snap3 = rcOmSnapshot.get(); Table snap3deletedTable = snap3.getMetadataManager().getDeletedTable(); diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestSnapshotCache.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestSnapshotCache.java index b2510ce76790..41d07fc47936 100644 --- a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestSnapshotCache.java +++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestSnapshotCache.java @@ -18,9 +18,8 @@ package org.apache.hadoop.ozone.om.snapshot; import com.google.common.cache.CacheLoader; -import org.apache.hadoop.ozone.om.IOmMetadataReader; +import org.apache.hadoop.ozone.om.OMMetrics; import org.apache.hadoop.ozone.om.OmSnapshot; -import org.apache.hadoop.ozone.om.OmSnapshotManager; import org.apache.ozone.test.GenericTestUtils; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; @@ -33,15 +32,15 @@ import org.slf4j.event.Level; import java.io.IOException; +import java.util.UUID; +import java.util.concurrent.TimeoutException; -import static org.apache.hadoop.ozone.om.helpers.SnapshotInfo.SnapshotStatus.SNAPSHOT_ACTIVE; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -52,23 +51,22 @@ class TestSnapshotCache { private static final int CACHE_SIZE_LIMIT = 3; - private static OmSnapshotManager omSnapshotManager; - private static CacheLoader cacheLoader; + private static CacheLoader cacheLoader; private SnapshotCache snapshotCache; + private OMMetrics omMetrics; + @BeforeAll static void beforeAll() throws Exception { - omSnapshotManager = mock(OmSnapshotManager.class); - when(omSnapshotManager.isSnapshotStatus(any(), eq(SNAPSHOT_ACTIVE))) - .thenReturn(true); cacheLoader = mock(CacheLoader.class); // Create a difference mock OmSnapshot instance each time load() is called when(cacheLoader.load(any())).thenAnswer( (Answer) invocation -> { final OmSnapshot omSnapshot = mock(OmSnapshot.class); // Mock the snapshotTable return value for the lookup inside release() - final String dbKey = (String) invocation.getArguments()[0]; - when(omSnapshot.getSnapshotTableKey()).thenReturn(dbKey); + final UUID snapshotID = (UUID) invocation.getArguments()[0]; + when(omSnapshot.getSnapshotTableKey()).thenReturn(snapshotID.toString()); + when(omSnapshot.getSnapshotID()).thenReturn(snapshotID); return omSnapshot; } @@ -81,8 +79,8 @@ static void beforeAll() throws Exception { @BeforeEach void setUp() { // Reset cache for each test case - snapshotCache = new SnapshotCache( - omSnapshotManager, cacheLoader, CACHE_SIZE_LIMIT); + omMetrics = OMMetrics.create(); + snapshotCache = new SnapshotCache(cacheLoader, CACHE_SIZE_LIMIT, omMetrics, 50); } @AfterEach @@ -92,115 +90,108 @@ void tearDown() { } @Test - @DisplayName("01. get()") + @DisplayName("get()") void testGet() throws IOException { - final String dbKey1 = "dbKey1"; - ReferenceCounted omSnapshot = - snapshotCache.get(dbKey1); + final UUID dbKey1 = UUID.randomUUID(); + assertEquals(0, omMetrics.getNumSnapshotCacheSize()); + ReferenceCounted omSnapshot = snapshotCache.get(dbKey1); assertNotNull(omSnapshot); assertNotNull(omSnapshot.get()); assertTrue(omSnapshot.get() instanceof OmSnapshot); assertEquals(1, snapshotCache.size()); + assertEquals(1, omMetrics.getNumSnapshotCacheSize()); } @Test - @DisplayName("02. get() same entry twice yields one cache entry only") + @DisplayName("get() same entry twice yields one cache entry only") void testGetTwice() throws IOException { - final String dbKey1 = "dbKey1"; - ReferenceCounted omSnapshot1 = - snapshotCache.get(dbKey1); + final UUID dbKey1 = UUID.randomUUID(); + ReferenceCounted omSnapshot1 = snapshotCache.get(dbKey1); assertNotNull(omSnapshot1); assertEquals(1, snapshotCache.size()); + assertEquals(1, omMetrics.getNumSnapshotCacheSize()); - ReferenceCounted omSnapshot1again = - snapshotCache.get(dbKey1); + ReferenceCounted omSnapshot1again = snapshotCache.get(dbKey1); // Should be the same instance assertEquals(omSnapshot1, omSnapshot1again); assertEquals(omSnapshot1.get(), omSnapshot1again.get()); assertEquals(1, snapshotCache.size()); + assertEquals(1, omMetrics.getNumSnapshotCacheSize()); } @Test - @DisplayName("03. release(String)") + @DisplayName("release(String)") void testReleaseByDbKey() throws IOException { - final String dbKey1 = "dbKey1"; - ReferenceCounted omSnapshot1 = - snapshotCache.get(dbKey1); + final UUID dbKey1 = UUID.randomUUID(); + ReferenceCounted omSnapshot1 = snapshotCache.get(dbKey1); assertNotNull(omSnapshot1); assertNotNull(omSnapshot1.get()); assertEquals(1, snapshotCache.size()); + assertEquals(1, omMetrics.getNumSnapshotCacheSize()); snapshotCache.release(dbKey1); // Entry will not be immediately evicted assertEquals(1, snapshotCache.size()); + assertEquals(1, omMetrics.getNumSnapshotCacheSize()); } @Test - @DisplayName("04. release(OmSnapshot)") - void testReleaseByOmSnapshotInstance() throws IOException { - final String dbKey1 = "dbKey1"; - ReferenceCounted omSnapshot1 = - snapshotCache.get(dbKey1); - assertNotNull(omSnapshot1); - assertEquals(1, snapshotCache.size()); - - snapshotCache.release((OmSnapshot) omSnapshot1.get()); - // Entry will not be immediately evicted - assertEquals(1, snapshotCache.size()); - } - - @Test - @DisplayName("05. invalidate()") + @DisplayName("invalidate()") void testInvalidate() throws IOException { - final String dbKey1 = "dbKey1"; - ReferenceCounted omSnapshot = - snapshotCache.get(dbKey1); + final UUID dbKey1 = UUID.randomUUID(); + ReferenceCounted omSnapshot = snapshotCache.get(dbKey1); assertNotNull(omSnapshot); assertEquals(1, snapshotCache.size()); + assertEquals(1, omMetrics.getNumSnapshotCacheSize()); snapshotCache.release(dbKey1); // Entry will not be immediately evicted assertEquals(1, snapshotCache.size()); + assertEquals(1, omMetrics.getNumSnapshotCacheSize()); snapshotCache.invalidate(dbKey1); assertEquals(0, snapshotCache.size()); + assertEquals(0, omMetrics.getNumSnapshotCacheSize()); } @Test - @DisplayName("06. invalidateAll()") + @DisplayName("invalidateAll()") void testInvalidateAll() throws IOException { - final String dbKey1 = "dbKey1"; - ReferenceCounted omSnapshot1 = - snapshotCache.get(dbKey1); + final UUID dbKey1 = UUID.randomUUID(); + ReferenceCounted omSnapshot1 = snapshotCache.get(dbKey1); assertNotNull(omSnapshot1); assertEquals(1, snapshotCache.size()); + assertEquals(1, omMetrics.getNumSnapshotCacheSize()); - final String dbKey2 = "dbKey2"; - ReferenceCounted omSnapshot2 = - snapshotCache.get(dbKey2); + final UUID dbKey2 = UUID.randomUUID(); + ReferenceCounted omSnapshot2 = snapshotCache.get(dbKey2); assertNotNull(omSnapshot2); assertEquals(2, snapshotCache.size()); + assertEquals(2, omMetrics.getNumSnapshotCacheSize()); // Should be difference omSnapshot instances assertNotEquals(omSnapshot1, omSnapshot2); - final String dbKey3 = "dbKey3"; - ReferenceCounted omSnapshot3 = - snapshotCache.get(dbKey3); + final UUID dbKey3 = UUID.randomUUID(); + ReferenceCounted omSnapshot3 = snapshotCache.get(dbKey3); assertNotNull(omSnapshot3); assertEquals(3, snapshotCache.size()); + assertEquals(3, omMetrics.getNumSnapshotCacheSize()); snapshotCache.release(dbKey1); // Entry will not be immediately evicted assertEquals(3, snapshotCache.size()); + assertEquals(3, omMetrics.getNumSnapshotCacheSize()); snapshotCache.invalidate(dbKey1); assertEquals(2, snapshotCache.size()); + assertEquals(2, omMetrics.getNumSnapshotCacheSize()); snapshotCache.invalidateAll(); assertEquals(0, snapshotCache.size()); + assertEquals(0, omMetrics.getNumSnapshotCacheSize()); } - private void assertEntryExistence(String key, boolean shouldExist) { + private void assertEntryExistence(UUID key, boolean shouldExist) { if (shouldExist) { snapshotCache.getDbMap().computeIfAbsent(key, k -> { fail(k + " should not have been evicted"); @@ -215,108 +206,132 @@ private void assertEntryExistence(String key, boolean shouldExist) { } @Test - @DisplayName("07. Basic cache eviction") - void testEviction1() throws IOException { + @DisplayName("Basic cache eviction") + void testEviction1() throws IOException, InterruptedException, TimeoutException { - final String dbKey1 = "dbKey1"; + final UUID dbKey1 = UUID.randomUUID(); snapshotCache.get(dbKey1); assertEquals(1, snapshotCache.size()); + assertEquals(1, omMetrics.getNumSnapshotCacheSize()); snapshotCache.release(dbKey1); assertEquals(1, snapshotCache.size()); + assertEquals(1, omMetrics.getNumSnapshotCacheSize()); - final String dbKey2 = "dbKey2"; + final UUID dbKey2 = UUID.randomUUID(); snapshotCache.get(dbKey2); assertEquals(2, snapshotCache.size()); + assertEquals(2, omMetrics.getNumSnapshotCacheSize()); snapshotCache.release(dbKey2); assertEquals(2, snapshotCache.size()); + assertEquals(2, omMetrics.getNumSnapshotCacheSize()); - final String dbKey3 = "dbKey3"; + final UUID dbKey3 = UUID.randomUUID(); snapshotCache.get(dbKey3); assertEquals(3, snapshotCache.size()); + assertEquals(3, omMetrics.getNumSnapshotCacheSize()); snapshotCache.release(dbKey3); assertEquals(3, snapshotCache.size()); + assertEquals(3, omMetrics.getNumSnapshotCacheSize()); - final String dbKey4 = "dbKey4"; + final UUID dbKey4 = UUID.randomUUID(); snapshotCache.get(dbKey4); - // dbKey1, dbKey2 and dbKey3 would have been evicted by the end of the last get() because - // those were release()d. + // dbKey1, dbKey2 and dbKey3 would have been evicted by the end of the last scheduled cleanup() because + // those were released. + GenericTestUtils.waitFor(() -> snapshotCache.size() == 1, 50, 3000); assertEquals(1, snapshotCache.size()); + assertEquals(1, omMetrics.getNumSnapshotCacheSize()); assertEntryExistence(dbKey1, false); } @Test - @DisplayName("08. Cache eviction while exceeding soft limit") - void testEviction2() throws IOException { + @DisplayName("Cache eviction while exceeding soft limit") + void testEviction2() throws IOException, InterruptedException, TimeoutException { - final String dbKey1 = "dbKey1"; + final UUID dbKey1 = UUID.randomUUID(); snapshotCache.get(dbKey1); assertEquals(1, snapshotCache.size()); + assertEquals(1, omMetrics.getNumSnapshotCacheSize()); - final String dbKey2 = "dbKey2"; + final UUID dbKey2 = UUID.randomUUID(); snapshotCache.get(dbKey2); assertEquals(2, snapshotCache.size()); + assertEquals(2, omMetrics.getNumSnapshotCacheSize()); - final String dbKey3 = "dbKey3"; + final UUID dbKey3 = UUID.randomUUID(); snapshotCache.get(dbKey3); assertEquals(3, snapshotCache.size()); + assertEquals(3, omMetrics.getNumSnapshotCacheSize()); - final String dbKey4 = "dbKey4"; + final UUID dbKey4 = UUID.randomUUID(); snapshotCache.get(dbKey4); // dbKey1 would not have been evicted because it is not release()d assertEquals(4, snapshotCache.size()); + assertEquals(4, omMetrics.getNumSnapshotCacheSize()); assertEntryExistence(dbKey1, true); // Releasing dbKey2 at this point should immediately trigger its eviction // because the cache size exceeded the soft limit snapshotCache.release(dbKey2); + GenericTestUtils.waitFor(() -> snapshotCache.size() == 3, 50, 3000); assertEquals(3, snapshotCache.size()); + assertEquals(3, omMetrics.getNumSnapshotCacheSize()); assertEntryExistence(dbKey2, false); assertEntryExistence(dbKey1, true); } @Test - @DisplayName("09. Cache eviction with try-with-resources") - void testEviction3WithClose() throws IOException { + @DisplayName("Cache eviction with try-with-resources") + void testEviction3WithClose() throws IOException, InterruptedException, TimeoutException { - final String dbKey1 = "dbKey1"; - try (ReferenceCounted rcOmSnapshot = snapshotCache.get(dbKey1)) { + final UUID dbKey1 = UUID.randomUUID(); + try (ReferenceCounted rcOmSnapshot = snapshotCache.get(dbKey1)) { assertEquals(1L, rcOmSnapshot.getTotalRefCount()); assertEquals(1, snapshotCache.size()); + assertEquals(1, omMetrics.getNumSnapshotCacheSize()); } // ref count should have been decreased because it would be close()d // upon exiting try-with-resources. assertEquals(0L, snapshotCache.getDbMap().get(dbKey1).getTotalRefCount()); assertEquals(1, snapshotCache.size()); + assertEquals(1, omMetrics.getNumSnapshotCacheSize()); - final String dbKey2 = "dbKey2"; - try (ReferenceCounted rcOmSnapshot = snapshotCache.get(dbKey2)) { + final UUID dbKey2 = UUID.randomUUID(); + try (ReferenceCounted rcOmSnapshot = snapshotCache.get(dbKey2)) { assertEquals(1L, rcOmSnapshot.getTotalRefCount()); assertEquals(2, snapshotCache.size()); + assertEquals(2, omMetrics.getNumSnapshotCacheSize()); // Get dbKey2 entry a second time - try (ReferenceCounted rcOmSnapshot2 = snapshotCache.get(dbKey2)) { + try (ReferenceCounted rcOmSnapshot2 = snapshotCache.get(dbKey2)) { assertEquals(2L, rcOmSnapshot.getTotalRefCount()); assertEquals(2L, rcOmSnapshot2.getTotalRefCount()); assertEquals(2, snapshotCache.size()); + assertEquals(2, omMetrics.getNumSnapshotCacheSize()); } assertEquals(1L, rcOmSnapshot.getTotalRefCount()); } assertEquals(0L, snapshotCache.getDbMap().get(dbKey2).getTotalRefCount()); assertEquals(2, snapshotCache.size()); + assertEquals(2, omMetrics.getNumSnapshotCacheSize()); - final String dbKey3 = "dbKey3"; - try (ReferenceCounted rcOmSnapshot = snapshotCache.get(dbKey3)) { + final UUID dbKey3 = UUID.randomUUID(); + try (ReferenceCounted rcOmSnapshot = snapshotCache.get(dbKey3)) { assertEquals(1L, rcOmSnapshot.getTotalRefCount()); assertEquals(3, snapshotCache.size()); + assertEquals(3, omMetrics.getNumSnapshotCacheSize()); } assertEquals(0L, snapshotCache.getDbMap().get(dbKey3).getTotalRefCount()); assertEquals(3, snapshotCache.size()); + assertEquals(3, omMetrics.getNumSnapshotCacheSize()); - final String dbKey4 = "dbKey4"; - try (ReferenceCounted rcOmSnapshot = snapshotCache.get(dbKey4)) { + final UUID dbKey4 = UUID.randomUUID(); + try (ReferenceCounted rcOmSnapshot = snapshotCache.get(dbKey4)) { + GenericTestUtils.waitFor(() -> snapshotCache.size() == 1, 50, 3000); assertEquals(1L, rcOmSnapshot.getTotalRefCount()); assertEquals(1, snapshotCache.size()); + assertEquals(1, omMetrics.getNumSnapshotCacheSize()); } assertEquals(0L, snapshotCache.getDbMap().get(dbKey4).getTotalRefCount()); assertEquals(1, snapshotCache.size()); + assertEquals(1, omMetrics.getNumSnapshotCacheSize()); } } diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestSnapshotDiffManager.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestSnapshotDiffManager.java index 5229ea46fbc6..786ab385ac39 100644 --- a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestSnapshotDiffManager.java +++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestSnapshotDiffManager.java @@ -39,7 +39,6 @@ import org.apache.hadoop.hdfs.DFSUtilClient; import org.apache.hadoop.hdfs.protocol.SnapshotDiffReport; import org.apache.hadoop.hdfs.protocol.SnapshotDiffReport.DiffReportEntry; -import org.apache.hadoop.ozone.om.IOmMetadataReader; import org.apache.hadoop.ozone.om.OMMetadataManager; import org.apache.hadoop.ozone.om.OMMetrics; import org.apache.hadoop.ozone.om.OmMetadataManagerImpl; @@ -63,7 +62,7 @@ import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.util.ClosableIterator; import org.apache.hadoop.util.ExitUtil; -import org.apache.ozone.rocksdb.util.ManagedSstFileReader; +import org.apache.ozone.rocksdb.util.SstFileSetReader; import org.apache.ozone.rocksdb.util.RdbUtil; import org.apache.ozone.rocksdiff.DifferSnapshotInfo; import org.apache.ozone.rocksdiff.RocksDBCheckpointDiffer; @@ -71,7 +70,7 @@ import org.apache.ozone.test.tag.Unhealthy; import org.apache.ratis.util.ExitUtils; import org.awaitility.Awaitility; -import org.jetbrains.annotations.NotNull; +import javax.annotation.Nonnull; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; @@ -84,7 +83,6 @@ import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; -import org.mockito.Matchers; import org.mockito.Mock; import org.mockito.MockedConstruction; import org.mockito.MockedStatic; @@ -215,9 +213,6 @@ public class TestSnapshotDiffManager { private OzoneManager ozoneManager; @Mock private OzoneConfiguration configuration; - - private SnapshotCache snapshotCache; - @Mock private Table snapshotInfoTable; @Mock @@ -326,8 +321,8 @@ public void init() throws RocksDBException, IOException, ExecutionException { OZONE_OM_SNAPSHOT_DIFF_JOB_DEFAULT_WAIT_TIME_DEFAULT, TimeUnit.MILLISECONDS)) .thenReturn(OZONE_OM_SNAPSHOT_DIFF_JOB_DEFAULT_WAIT_TIME_DEFAULT); - when(configuration - .getBoolean(OZONE_OM_SNAPSHOT_FORCE_FULL_DIFF, + when(configuration. + getBoolean(OZONE_OM_SNAPSHOT_FORCE_FULL_DIFF, OZONE_OM_SNAPSHOT_FORCE_FULL_DIFF_DEFAULT)) .thenReturn(OZONE_OM_SNAPSHOT_FORCE_FULL_DIFF_DEFAULT); when(configuration @@ -380,23 +375,29 @@ public void init() throws RocksDBException, IOException, ExecutionException { when(ozoneManager.getConfiguration()).thenReturn(configuration); when(ozoneManager.getMetadataManager()).thenReturn(omMetadataManager); - CacheLoader loader = - new CacheLoader() { - @NotNull - @Override - public OmSnapshot load(@NotNull String key) { - return getMockedOmSnapshot(key); - } - }; - - omSnapshotManager = Mockito.mock(OmSnapshotManager.class); - Mockito.when(omSnapshotManager.isSnapshotStatus( - Matchers.any(), Matchers.any())).thenReturn(true); - snapshotCache = new SnapshotCache(omSnapshotManager, loader, 10); + omSnapshotManager = mock(OmSnapshotManager.class); + when(omSnapshotManager.isSnapshotStatus(any(), any())).thenReturn(true); + SnapshotCache snapshotCache = new SnapshotCache(mockCacheLoader(), 10, omMetrics, 0); + when(omSnapshotManager.getActiveSnapshot(anyString(), anyString(), anyString())) + .thenAnswer(invocationOnMock -> { + SnapshotInfo snapInfo = SnapshotUtils.getSnapshotInfo(ozoneManager, invocationOnMock.getArgument(0), + invocationOnMock.getArgument(1), invocationOnMock.getArgument(2)); + return snapshotCache.get(snapInfo.getSnapshotId()); + }); + when(ozoneManager.getOmSnapshotManager()).thenReturn(omSnapshotManager); snapshotDiffManager = new SnapshotDiffManager(db, differ, ozoneManager, - snapshotCache, snapDiffJobTable, snapDiffReportTable, - columnFamilyOptions, codecRegistry); + snapDiffJobTable, snapDiffReportTable, columnFamilyOptions, codecRegistry); + } + + private CacheLoader mockCacheLoader() { + return new CacheLoader() { + @Nonnull + @Override + public OmSnapshot load(@Nonnull UUID key) { + return getMockedOmSnapshot(key); + } + }; } @AfterEach @@ -411,11 +412,12 @@ public void tearDown() { IOUtils.closeQuietly(snapshotDiffManager); } - private OmSnapshot getMockedOmSnapshot(String snapshot) { + private OmSnapshot getMockedOmSnapshot(UUID snapshotId) { OmSnapshot omSnapshot = mock(OmSnapshot.class); - when(omSnapshot.getName()).thenReturn(snapshot); + when(omSnapshot.getName()).thenReturn(snapshotId.toString()); when(omSnapshot.getMetadataManager()).thenReturn(omMetadataManager); when(omMetadataManager.getStore()).thenReturn(dbStore); + when(omSnapshot.getSnapshotID()).thenReturn(snapshotId); return omSnapshot; } @@ -430,6 +432,10 @@ private SnapshotInfo getMockedSnapshotInfo(UUID snapshotId) { public void testGetDeltaFilesWithDag(int numberOfFiles) throws IOException { UUID snap1 = UUID.randomUUID(); UUID snap2 = UUID.randomUUID(); + when(snapshotInfoTable.get(SnapshotInfo.getTableKey(VOLUME_NAME, BUCKET_NAME, snap1.toString()))) + .thenReturn(getSnapshotInfoInstance(VOLUME_NAME, BUCKET_NAME, snap1.toString(), snap2)); + when(snapshotInfoTable.get(SnapshotInfo.getTableKey(VOLUME_NAME, BUCKET_NAME, snap2.toString()))) + .thenReturn(getSnapshotInfoInstance(VOLUME_NAME, BUCKET_NAME, snap2.toString(), snap2)); String diffDir = Files.createTempDirectory("snapdiff_dir").toString(); Set randomStrings = IntStream.range(0, numberOfFiles) @@ -442,12 +448,12 @@ public void testGetDeltaFilesWithDag(int numberOfFiles) throws IOException { eq(diffDir)) ).thenReturn(Lists.newArrayList(randomStrings)); - ReferenceCounted rcFromSnapshot = - snapshotCache.get(snap1.toString()); - ReferenceCounted rcToSnapshot = - snapshotCache.get(snap2.toString()); - OmSnapshot fromSnapshot = (OmSnapshot) rcFromSnapshot.get(); - OmSnapshot toSnapshot = (OmSnapshot) rcToSnapshot.get(); + ReferenceCounted rcFromSnapshot = + omSnapshotManager.getActiveSnapshot(VOLUME_NAME, BUCKET_NAME, snap1.toString()); + ReferenceCounted rcToSnapshot = + omSnapshotManager.getActiveSnapshot(VOLUME_NAME, BUCKET_NAME, snap2.toString()); + OmSnapshot fromSnapshot = rcFromSnapshot.get(); + OmSnapshot toSnapshot = rcToSnapshot.get(); SnapshotInfo fromSnapshotInfo = getMockedSnapshotInfo(snap1); SnapshotInfo toSnapshotInfo = getMockedSnapshotInfo(snap2); @@ -500,6 +506,10 @@ public void testGetDeltaFilesWithFullDiff(int numberOfFiles, }); UUID snap1 = UUID.randomUUID(); UUID snap2 = UUID.randomUUID(); + when(snapshotInfoTable.get(SnapshotInfo.getTableKey(VOLUME_NAME, BUCKET_NAME, snap1.toString()))) + .thenReturn(getSnapshotInfoInstance(VOLUME_NAME, BUCKET_NAME, snap1.toString(), snap2)); + when(snapshotInfoTable.get(SnapshotInfo.getTableKey(VOLUME_NAME, BUCKET_NAME, snap2.toString()))) + .thenReturn(getSnapshotInfoInstance(VOLUME_NAME, BUCKET_NAME, snap2.toString(), snap2)); if (!useFullDiff) { when(differ.getSSTDiffListWithFullPath( any(DifferSnapshotInfo.class), @@ -508,12 +518,12 @@ public void testGetDeltaFilesWithFullDiff(int numberOfFiles, .thenReturn(Collections.emptyList()); } - ReferenceCounted rcFromSnapshot = - snapshotCache.get(snap1.toString()); - ReferenceCounted rcToSnapshot = - snapshotCache.get(snap2.toString()); - OmSnapshot fromSnapshot = (OmSnapshot) rcFromSnapshot.get(); - OmSnapshot toSnapshot = (OmSnapshot) rcToSnapshot.get(); + ReferenceCounted rcFromSnapshot = + omSnapshotManager.getActiveSnapshot(VOLUME_NAME, BUCKET_NAME, snap1.toString()); + ReferenceCounted rcToSnapshot = + omSnapshotManager.getActiveSnapshot(VOLUME_NAME, BUCKET_NAME, snap2.toString()); + OmSnapshot fromSnapshot = rcFromSnapshot.get(); + OmSnapshot toSnapshot = rcToSnapshot.get(); SnapshotInfo fromSnapshotInfo = getMockedSnapshotInfo(snap1); SnapshotInfo toSnapshotInfo = getMockedSnapshotInfo(snap1); @@ -565,6 +575,10 @@ public void testGetDeltaFilesWithDifferThrowException(int numberOfFiles) }); UUID snap1 = UUID.randomUUID(); UUID snap2 = UUID.randomUUID(); + when(snapshotInfoTable.get(SnapshotInfo.getTableKey(VOLUME_NAME, BUCKET_NAME, snap1.toString()))) + .thenReturn(getSnapshotInfoInstance(VOLUME_NAME, BUCKET_NAME, snap1.toString(), snap1)); + when(snapshotInfoTable.get(SnapshotInfo.getTableKey(VOLUME_NAME, BUCKET_NAME, snap2.toString()))) + .thenReturn(getSnapshotInfoInstance(VOLUME_NAME, BUCKET_NAME, snap2.toString(), snap2)); doThrow(new FileNotFoundException("File not found exception.")) .when(differ) @@ -573,12 +587,12 @@ public void testGetDeltaFilesWithDifferThrowException(int numberOfFiles) any(DifferSnapshotInfo.class), anyString()); - ReferenceCounted rcFromSnapshot = - snapshotCache.get(snap1.toString()); - ReferenceCounted rcToSnapshot = - snapshotCache.get(snap2.toString()); - OmSnapshot fromSnapshot = (OmSnapshot) rcFromSnapshot.get(); - OmSnapshot toSnapshot = (OmSnapshot) rcToSnapshot.get(); + ReferenceCounted rcFromSnapshot = + omSnapshotManager.getActiveSnapshot(VOLUME_NAME, BUCKET_NAME, snap1.toString()); + ReferenceCounted rcToSnapshot = + omSnapshotManager.getActiveSnapshot(VOLUME_NAME, BUCKET_NAME, snap2.toString()); + OmSnapshot fromSnapshot = rcFromSnapshot.get(); + OmSnapshot toSnapshot = rcToSnapshot.get(); SnapshotInfo fromSnapshotInfo = getMockedSnapshotInfo(snap1); SnapshotInfo toSnapshotInfo = getMockedSnapshotInfo(snap1); @@ -652,8 +666,8 @@ public void testObjectIdMapWithTombstoneEntries(boolean nativeLibraryLoaded, .map(i -> (i + 100) + "/key" + i).collect(Collectors.toSet()); // Mocking SSTFileReader functions to return the above keys list. - try (MockedConstruction mockedSSTFileReader = - Mockito.mockConstruction(ManagedSstFileReader.class, + try (MockedConstruction mockedSSTFileReader = + Mockito.mockConstruction(SstFileSetReader.class, (mock, context) -> { when(mock.getKeyStreamWithTombstone(any(), any(), any())) .thenReturn(keysIncludingTombstones.stream()); @@ -682,8 +696,7 @@ public void testObjectIdMapWithTombstoneEntries(boolean nativeLibraryLoaded, getMockedTable(fromSnapshotTableMap, snapshotTableName); snapshotDiffManager = new SnapshotDiffManager(db, differ, ozoneManager, - snapshotCache, snapDiffJobTable, snapDiffReportTable, - columnFamilyOptions, codecRegistry); + snapDiffJobTable, snapDiffReportTable, columnFamilyOptions, codecRegistry); SnapshotDiffManager spy = spy(snapshotDiffManager); doAnswer(invocation -> { diff --git a/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicOzoneClientAdapterImpl.java b/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicOzoneClientAdapterImpl.java index e6892d9784db..9cd09ea1f3b2 100644 --- a/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicOzoneClientAdapterImpl.java +++ b/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicOzoneClientAdapterImpl.java @@ -45,7 +45,6 @@ import org.apache.hadoop.hdds.security.SecurityConfig; import org.apache.hadoop.hdfs.protocol.SnapshotDiffReport; import org.apache.hadoop.io.Text; -import org.apache.hadoop.ozone.OFSPath; import org.apache.hadoop.ozone.OmUtils; import org.apache.hadoop.ozone.OzoneConfigKeys; import org.apache.hadoop.ozone.client.ObjectStore; @@ -598,18 +597,14 @@ public FileChecksum getFileChecksum(String keyName, long length) @Override public String createSnapshot(String pathStr, String snapshotName) throws IOException { - OFSPath ofsPath = new OFSPath(pathStr, config); - return objectStore.createSnapshot(ofsPath.getVolumeName(), - ofsPath.getBucketName(), - snapshotName); + return objectStore.createSnapshot(volume.getName(), bucket.getName(), snapshotName); } @Override public void deleteSnapshot(String pathStr, String snapshotName) throws IOException { - OFSPath ofsPath = new OFSPath(pathStr, config); - objectStore.deleteSnapshot(ofsPath.getVolumeName(), - ofsPath.getBucketName(), + objectStore.deleteSnapshot(volume.getName(), + bucket.getName(), snapshotName); } @@ -652,12 +647,11 @@ public SnapshotDiffReport getSnapshotDiffReport(Path snapshotDir, } finally { // delete the temp snapshot if (takeTemporaryToSnapshot || takeTemporaryFromSnapshot) { - OFSPath snapPath = new OFSPath(snapshotDir.toString(), config); if (takeTemporaryToSnapshot) { - OzoneClientUtils.deleteSnapshot(objectStore, toSnapshot, snapPath); + OzoneClientUtils.deleteSnapshot(objectStore, toSnapshot, volume.getName(), bucket.getName()); } if (takeTemporaryFromSnapshot) { - OzoneClientUtils.deleteSnapshot(objectStore, fromSnapshot, snapPath); + OzoneClientUtils.deleteSnapshot(objectStore, fromSnapshot, volume.getName(), bucket.getName()); } } } @@ -696,8 +690,7 @@ public void setTimes(String key, long mtime, long atime) throws IOException { @Override public boolean isFileClosed(String pathStr) throws IOException { incrementCounter(Statistic.INVOCATION_IS_FILE_CLOSED, 1); - OFSPath ofsPath = new OFSPath(pathStr, config); - if (!ofsPath.isKey()) { + if (StringUtils.isEmpty(pathStr)) { throw new IOException("not a file"); } OzoneFileStatus status = bucket.getFileStatus(pathStr); diff --git a/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicRootedOzoneClientAdapterImpl.java b/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicRootedOzoneClientAdapterImpl.java index 506862f48862..44777cda5c05 100644 --- a/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicRootedOzoneClientAdapterImpl.java +++ b/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicRootedOzoneClientAdapterImpl.java @@ -1301,10 +1301,10 @@ public SnapshotDiffReport getSnapshotDiffReport(Path snapshotDir, } finally { // delete the temp snapshot if (takeTemporaryToSnapshot) { - OzoneClientUtils.deleteSnapshot(objectStore, toSnapshot, ofsPath); + OzoneClientUtils.deleteSnapshot(objectStore, toSnapshot, volume, bucket); } if (takeTemporaryFromSnapshot) { - OzoneClientUtils.deleteSnapshot(objectStore, fromSnapshot, ofsPath); + OzoneClientUtils.deleteSnapshot(objectStore, fromSnapshot, volume, bucket); } } } diff --git a/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/OzoneClientUtils.java b/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/OzoneClientUtils.java index 4cb9084b2b91..383ad6db495b 100644 --- a/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/OzoneClientUtils.java +++ b/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/OzoneClientUtils.java @@ -25,7 +25,6 @@ import org.apache.hadoop.hdds.protocol.proto.HddsProtos; import org.apache.hadoop.fs.FileChecksum; import org.apache.hadoop.hdds.scm.OzoneClientConfig; -import org.apache.hadoop.ozone.OFSPath; import org.apache.hadoop.ozone.client.checksum.BaseFileChecksumHelper; import org.apache.hadoop.ozone.client.ObjectStore; import org.apache.hadoop.ozone.client.OzoneBucket; @@ -270,14 +269,14 @@ public static int limitValue(int confValue, String confName, int maxLimit) { } public static void deleteSnapshot(ObjectStore objectStore, - String snapshot, OFSPath snapPath) { + String snapshot, String volumeName, String bucketName) { try { - objectStore.deleteSnapshot(snapPath.getVolumeName(), - snapPath.getBucketName(), snapshot); + objectStore.deleteSnapshot(volumeName, + bucketName, snapshot); } catch (IOException exception) { LOG.warn("Failed to delete the temp snapshot with name {} in bucket" + " {} and volume {} after snapDiff op. Exception : {}", snapshot, - snapPath.getBucketName(), snapPath.getVolumeName(), + bucketName, volumeName, exception.getMessage()); } } diff --git a/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/OzoneFSStorageStatistics.java b/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/OzoneFSStorageStatistics.java index 27b216a83149..12a3570ea001 100644 --- a/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/OzoneFSStorageStatistics.java +++ b/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/OzoneFSStorageStatistics.java @@ -18,6 +18,7 @@ package org.apache.hadoop.fs.ozone; +import com.google.common.annotations.VisibleForTesting; import org.apache.hadoop.hdds.annotation.InterfaceAudience; import org.apache.hadoop.hdds.annotation.InterfaceStability; import org.apache.hadoop.fs.StorageStatistics; @@ -25,6 +26,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.EnumMap; @@ -123,4 +125,11 @@ public void reset() { } } + @VisibleForTesting + Map snapshot() { + Map snapshot = new HashMap<>(); + opsCount.forEach((k, v) -> snapshot.put(k.getSymbol(), v.longValue())); + return snapshot; + } + } diff --git a/hadoop-ozone/pom.xml b/hadoop-ozone/pom.xml index 686d3bbf9a8a..58f86207a00b 100644 --- a/hadoop-ozone/pom.xml +++ b/hadoop-ozone/pom.xml @@ -26,8 +26,6 @@ apache/ozone:${project.version} - 5.3.27 - 3.11.10 interface-client diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconConstants.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconConstants.java index 134092146e54..9c79a869c41d 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconConstants.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconConstants.java @@ -36,6 +36,11 @@ private ReconConstants() { public static final String RECON_SCM_SNAPSHOT_DB = "scm.snapshot.db"; // By default, limit the number of results returned + + /** + * The maximum number of top disk usage records to return in a /du response. + */ + public static final int DISK_USAGE_TOP_RECORDS_LIMIT = 30; public static final String DEFAULT_OPEN_KEY_INCLUDE_NON_FSO = "false"; public static final String DEFAULT_OPEN_KEY_INCLUDE_FSO = "false"; public static final String DEFAULT_FETCH_COUNT = "1000"; diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconServerConfigKeys.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconServerConfigKeys.java index b3c601c4c1fc..ab87bda4412c 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconServerConfigKeys.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconServerConfigKeys.java @@ -168,6 +168,23 @@ public final class ReconServerConfigKeys { public static final String OZONE_RECON_SCM_SNAPSHOT_TASK_INITIAL_DELAY_DEFAULT = "1m"; + + public static final String OZONE_RECON_SCM_CLIENT_RPC_TIME_OUT_KEY = + "ozone.recon.scmclient.rpc.timeout"; + + public static final String OZONE_RECON_SCM_CLIENT_RPC_TIME_OUT_DEFAULT = "1m"; + + public static final String OZONE_RECON_SCM_CLIENT_MAX_RETRY_TIMEOUT_KEY = + "ozone.recon.scmclient.max.retry.timeout"; + + public static final String OZONE_RECON_SCM_CLIENT_MAX_RETRY_TIMEOUT_DEFAULT = + "6s"; + + public static final String OZONE_RECON_SCM_CLIENT_FAILOVER_MAX_RETRY_KEY = + "ozone.recon.scmclient.failover.max.retry"; + + public static final int + OZONE_RECON_SCM_CLIENT_FAILOVER_MAX_RETRY_DEFAULT = 3; /** * Private constructor for utility class. */ diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconUtils.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconUtils.java index 0d0c57fbe36f..3e95006ed605 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconUtils.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconUtils.java @@ -29,6 +29,13 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; import com.google.common.base.Preconditions; import com.google.inject.Singleton; @@ -36,7 +43,9 @@ import org.apache.hadoop.hdds.HddsUtils; import org.apache.hadoop.hdds.conf.ConfigurationSource; import org.apache.hadoop.hdds.conf.OzoneConfiguration; +import org.apache.hadoop.hdds.scm.ScmUtils; import org.apache.hadoop.hdds.scm.ha.SCMNodeDetails; +import org.apache.hadoop.hdds.scm.server.SCMDatanodeHeartbeatDispatcher; import org.apache.hadoop.hdds.utils.HddsServerUtil; import org.apache.hadoop.hdfs.web.URLConnectionFactory; import org.apache.hadoop.io.IOUtils; @@ -44,16 +53,29 @@ import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; + +import static org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_EVENT_CONTAINER_REPORT_QUEUE_SIZE_DEFAULT; +import static org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_EVENT_THREAD_POOL_SIZE_DEFAULT; import static org.apache.hadoop.hdds.server.ServerUtils.getDirectoryFromConfig; import static org.apache.hadoop.hdds.server.ServerUtils.getOzoneMetaDirPath; +import static org.apache.hadoop.ozone.OzoneConsts.OM_KEY_PREFIX; import static org.apache.hadoop.ozone.recon.ReconServerConfigKeys.OZONE_RECON_SCM_DB_DIR; import static org.jooq.impl.DSL.currentTimestamp; import static org.jooq.impl.DSL.select; import static org.jooq.impl.DSL.using; +import org.apache.hadoop.ozone.OmUtils; +import org.apache.hadoop.ozone.om.helpers.OmKeyInfo; +import org.apache.hadoop.ozone.recon.api.types.NSSummary; +import org.apache.hadoop.ozone.recon.api.types.DUResponse; +import org.apache.hadoop.ozone.recon.recovery.ReconOMMetadataManager; +import org.apache.hadoop.ozone.recon.scm.ReconContainerReportQueue; +import org.apache.hadoop.ozone.recon.spi.ReconNamespaceSummaryManager; import org.apache.hadoop.security.authentication.client.AuthenticationException; import org.hadoop.ozone.recon.schema.tables.daos.GlobalStatsDao; import org.hadoop.ozone.recon.schema.tables.pojos.GlobalStats; +import org.jetbrains.annotations.NotNull; +import com.google.common.annotations.VisibleForTesting; import org.jooq.Configuration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -69,13 +91,34 @@ public class ReconUtils { public ReconUtils() { } - private static final Logger LOG = LoggerFactory.getLogger( + private static Logger log = LoggerFactory.getLogger( ReconUtils.class); + private static AtomicBoolean rebuildTriggered = new AtomicBoolean(false); + public static File getReconScmDbDir(ConfigurationSource conf) { return new ReconUtils().getReconDbDir(conf, OZONE_RECON_SCM_DB_DIR); } + @NotNull + public static List> initContainerReportQueue( + OzoneConfiguration configuration) { + int threadPoolSize = + configuration.getInt(ScmUtils.getContainerReportConfPrefix() + + ".thread.pool.size", + OZONE_SCM_EVENT_THREAD_POOL_SIZE_DEFAULT); + int queueSize = configuration.getInt( + ScmUtils.getContainerReportConfPrefix() + ".queue.size", + OZONE_SCM_EVENT_CONTAINER_REPORT_QUEUE_SIZE_DEFAULT); + List> queues = + new ArrayList<>(); + for (int i = 0; i < threadPoolSize; ++i) { + queues.add(new ReconContainerReportQueue(queueSize)); + } + return queues; + } + /** * Get configured Recon DB directory value based on config. If not present, * fallback to ozone.metadata.dirs @@ -92,7 +135,7 @@ public File getReconDbDir(ConfigurationSource conf, String dirConfigKey) { return metadataDir; } - LOG.warn("{} is not configured. We recommend adding this setting. " + + log.warn("{} is not configured. We recommend adding this setting. " + "Falling back to {} instead.", dirConfigKey, HddsConfigKeys.OZONE_METADATA_DIRS); return getOzoneMetaDirPath(conf); @@ -127,7 +170,7 @@ public static File createTarFile(Path sourcePath) throws IOException { org.apache.hadoop.io.IOUtils.closeStream(tarOs); org.apache.hadoop.io.IOUtils.closeStream(fileOutputStream); } catch (Exception e) { - LOG.error("Exception encountered when closing " + + log.error("Exception encountered when closing " + "TAR file output stream: " + e); } } @@ -192,7 +235,7 @@ public void untarCheckpointFile(File tarFile, Path destPath) if (entry.isDirectory()) { boolean success = f.mkdirs(); if (!success) { - LOG.error("Unable to create directory found in tar."); + log.error("Unable to create directory found in tar."); } } else { //Write contents of file in archive to a new file. @@ -215,25 +258,103 @@ public void untarCheckpointFile(File tarFile, Path destPath) } } + + /** + * Constructs the full path of a key from its OmKeyInfo using a bottom-up approach, starting from the leaf node. + * + * The method begins with the leaf node (the key itself) and recursively prepends parent directory names, fetched + * via NSSummary objects, until reaching the parent bucket (parentId is -1). It effectively builds the path from + * bottom to top, finally prepending the volume and bucket names to complete the full path. If the directory structure + * is currently being rebuilt (indicated by the rebuildTriggered flag), this method returns an empty string to signify + * that path construction is temporarily unavailable. + * + * @param omKeyInfo The OmKeyInfo object for the key + * @return The constructed full path of the key as a String, or an empty string if a rebuild is in progress and + * the path cannot be constructed at this time. + * @throws IOException + */ + public static String constructFullPath(OmKeyInfo omKeyInfo, + ReconNamespaceSummaryManager reconNamespaceSummaryManager, + ReconOMMetadataManager omMetadataManager) + throws IOException { + + StringBuilder fullPath = new StringBuilder(omKeyInfo.getKeyName()); + long parentId = omKeyInfo.getParentObjectID(); + boolean isDirectoryPresent = false; + + while (parentId != 0) { + NSSummary nsSummary = reconNamespaceSummaryManager.getNSSummary(parentId); + if (nsSummary == null) { + log.warn("NSSummary tree is currently being rebuilt or the directory could be in the progress of " + + "deletion, returning empty string for path construction."); + return ""; + } + if (nsSummary.getParentId() == -1) { + if (rebuildTriggered.compareAndSet(false, true)) { + triggerRebuild(reconNamespaceSummaryManager, omMetadataManager); + } + log.warn("NSSummary tree is currently being rebuilt, returning empty string for path construction."); + return ""; + } + fullPath.insert(0, nsSummary.getDirName() + OM_KEY_PREFIX); + + // Move to the parent ID of the current directory + parentId = nsSummary.getParentId(); + isDirectoryPresent = true; + } + + // Prepend the volume and bucket to the constructed path + String volumeName = omKeyInfo.getVolumeName(); + String bucketName = omKeyInfo.getBucketName(); + fullPath.insert(0, volumeName + OM_KEY_PREFIX + bucketName + OM_KEY_PREFIX); + if (isDirectoryPresent) { + return OmUtils.normalizeKey(fullPath.toString(), true); + } + return fullPath.toString(); + } + + private static void triggerRebuild(ReconNamespaceSummaryManager reconNamespaceSummaryManager, + ReconOMMetadataManager omMetadataManager) { + ExecutorService executor = Executors.newSingleThreadExecutor(r -> { + Thread t = new Thread(r); + t.setName("RebuildNSSummaryThread"); + return t; + }); + + executor.submit(() -> { + long startTime = System.currentTimeMillis(); + log.info("Rebuilding NSSummary tree..."); + try { + reconNamespaceSummaryManager.rebuildNSSummaryTree(omMetadataManager); + } finally { + long endTime = System.currentTimeMillis(); + log.info("NSSummary tree rebuild completed in {} ms.", endTime - startTime); + } + }); + executor.shutdown(); + } + /** * Make HTTP GET call on the URL and return HttpURLConnection instance. + * * @param connectionFactory URLConnectionFactory to use. - * @param url url to call - * @param isSpnego is SPNEGO enabled + * @param url url to call + * @param isSpnego is SPNEGO enabled * @return HttpURLConnection instance of the HTTP call. * @throws IOException, AuthenticationException While reading the response. */ public HttpURLConnection makeHttpCall(URLConnectionFactory connectionFactory, - String url, boolean isSpnego) + String url, boolean isSpnego) throws IOException, AuthenticationException { HttpURLConnection urlConnection = (HttpURLConnection) - connectionFactory.openConnection(new URL(url), isSpnego); + connectionFactory.openConnection(new URL(url), isSpnego); urlConnection.connect(); return urlConnection; } /** * Load last known DB in Recon. + * * @param reconDbDir * @param fileNamePrefix * @return @@ -258,7 +379,7 @@ public File getLastKnownDB(File reconDbDir, String fileNamePrefix) { lastKnownSnapshotFileName = fileName; } } catch (NumberFormatException nfEx) { - LOG.warn("Unknown file found in Recon DB dir : {}", fileName); + log.warn("Unknown file found in Recon DB dir : {}", fileName); } } } @@ -293,6 +414,33 @@ public static void upsertGlobalStatsTable(Configuration sqlConfiguration, } } + /** + * Sorts a list of DiskUsage objects in descending order by size using parallel sorting and + * returns the top N records as specified by the limit. + * + * This method is optimized for large datasets and utilizes parallel processing to efficiently + * sort and retrieve the top N largest records by size. It's especially useful for reducing + * processing time and memory usage when only a subset of sorted records is needed. + * + * Advantages of this approach include: + * - Efficient handling of large datasets by leveraging multi-core processors. + * - Reduction in memory usage and improvement in processing time by limiting the + * number of returned records. + * - Scalability and easy integration with existing systems. + * + * @param diskUsageList the list of DiskUsage objects to be sorted. + * @param limit the maximum number of DiskUsage objects to return. + * @return a list of the top N DiskUsage objects sorted in descending order by size, + * where N is the specified limit. + */ + public static List sortDiskUsageDescendingWithLimit( + List diskUsageList, int limit) { + return diskUsageList.parallelStream() + .sorted((du1, du2) -> Long.compare(du2.getSize(), du1.getSize())) + .limit(limit) + .collect(Collectors.toList()); + } + public static long getFileSizeUpperBound(long fileSize) { if (fileSize >= ReconConstants.MAX_FILE_SIZE_UPPER_BOUND) { return Long.MAX_VALUE; @@ -356,4 +504,9 @@ public SCMNodeDetails getReconNodeDetails(OzoneConfiguration conf) { HddsServerUtil.getReconDataNodeBindAddress(conf)); return builder.build(); } + + @VisibleForTesting + public static void setLogger(Logger logger) { + log = logger; + } } diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/ClusterStateEndpoint.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/ClusterStateEndpoint.java index bc87c402eb29..b074e5ba56a6 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/ClusterStateEndpoint.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/ClusterStateEndpoint.java @@ -120,7 +120,8 @@ public Response getClusterState() { SCMNodeStat stats = nodeManager.getStats(); DatanodeStorageReport storageReport = new DatanodeStorageReport(stats.getCapacity().get(), - stats.getScmUsed().get(), stats.getRemaining().get()); + stats.getScmUsed().get(), stats.getRemaining().get(), + stats.getCommitted().get()); ClusterStateResponse.Builder builder = ClusterStateResponse.newBuilder(); GlobalStats volumeRecord = globalStatsDao.findById( diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/ContainerEndpoint.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/ContainerEndpoint.java index d838e9c36e57..6a37dfd5d72a 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/ContainerEndpoint.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/ContainerEndpoint.java @@ -31,6 +31,7 @@ import org.apache.hadoop.ozone.om.helpers.OmKeyInfo; import org.apache.hadoop.ozone.om.helpers.OmKeyLocationInfo; import org.apache.hadoop.ozone.om.helpers.OmKeyLocationInfoGroup; +import org.apache.hadoop.ozone.recon.ReconUtils; import org.apache.hadoop.ozone.recon.api.types.ContainerDiscrepancyInfo; import org.apache.hadoop.ozone.recon.api.types.ContainerKeyPrefix; import org.apache.hadoop.ozone.recon.api.types.ContainerMetadata; @@ -94,10 +95,7 @@ @AdminOnly public class ContainerEndpoint { - @Inject private ReconContainerMetadataManager reconContainerMetadataManager; - - @Inject private ReconOMMetadataManager omMetadataManager; private final ReconContainerManager containerManager; @@ -144,33 +142,38 @@ public static DataFilter fromValue(String value) { @Inject public ContainerEndpoint(OzoneStorageContainerManager reconSCM, - ContainerHealthSchemaManager containerHealthSchemaManager, - ReconNamespaceSummaryManager reconNamespaceSummaryManager) { + ContainerHealthSchemaManager containerHealthSchemaManager, + ReconNamespaceSummaryManager reconNamespaceSummaryManager, + ReconContainerMetadataManager reconContainerMetadataManager, + ReconOMMetadataManager omMetadataManager) { this.containerManager = (ReconContainerManager) reconSCM.getContainerManager(); this.pipelineManager = reconSCM.getPipelineManager(); this.containerHealthSchemaManager = containerHealthSchemaManager; this.reconNamespaceSummaryManager = reconNamespaceSummaryManager; this.reconSCM = reconSCM; + this.reconContainerMetadataManager = reconContainerMetadataManager; + this.omMetadataManager = omMetadataManager; } /** * Return @{@link org.apache.hadoop.hdds.scm.container} * for the containers starting from the given "prev-key" query param for the * given "limit". The given "prev-key" is skipped from the results returned. + * * @param prevKey the containerID after which results are returned. * start containerID, >=0, * start searching at the head if 0. - * @param limit max no. of containers to get. - * count must be >= 0 - * Usually the count will be replace with a very big - * value instead of being unlimited in case the db is very big. + * @param limit max no. of containers to get. + * count must be >= 0 + * Usually the count will be replace with a very big + * value instead of being unlimited in case the db is very big. * @return {@link Response} */ @GET public Response getContainers( @DefaultValue(DEFAULT_FETCH_COUNT) @QueryParam(RECON_QUERY_LIMIT) - int limit, + int limit, @DefaultValue(PREV_CONTAINER_ID_DEFAULT_VALUE) @QueryParam(RECON_QUERY_PREVKEY) long prevKey) { if (limit < 0 || prevKey < 0) { @@ -212,8 +215,8 @@ public Response getContainers( * starting from the given "prev-key" query param for the given "limit". * The given prevKeyPrefix is skipped from the results returned. * - * @param containerID the given containerID. - * @param limit max no. of keys to get. + * @param containerID the given containerID. + * @param limit max no. of keys to get. * @param prevKeyPrefix the key prefix after which results are returned. * @return {@link Response} */ @@ -226,7 +229,12 @@ public Response getKeysForContainer( @DefaultValue(StringUtils.EMPTY) @QueryParam(RECON_QUERY_PREVKEY) String prevKeyPrefix) { Map keyMetadataMap = new LinkedHashMap<>(); + + // Total count of keys in the container. long totalCount; + // Last key prefix to be used for pagination. It will be exposed in the response. + String lastKey = ""; + try { Map containerKeyPrefixMap = reconContainerMetadataManager.getKeyPrefixesForContainer(containerID, @@ -263,6 +271,7 @@ public Response getKeysForContainer( omKeyInfo.getVolumeName(), omKeyInfo.getBucketName(), omKeyInfo.getKeyName()); + lastKey = ozoneKey; if (keyMetadataMap.containsKey(ozoneKey)) { keyMetadataMap.get(ozoneKey).getVersions() .add(containerKeyPrefix.getKeyVersion()); @@ -278,6 +287,8 @@ public Response getKeysForContainer( keyMetadata.setBucket(omKeyInfo.getBucketName()); keyMetadata.setVolume(omKeyInfo.getVolumeName()); keyMetadata.setKey(omKeyInfo.getKeyName()); + keyMetadata.setCompletePath(ReconUtils.constructFullPath(omKeyInfo, + reconNamespaceSummaryManager, omMetadataManager)); keyMetadata.setCreationTime( Instant.ofEpochMilli(omKeyInfo.getCreationTime())); keyMetadata.setModificationTime( @@ -298,7 +309,7 @@ public Response getKeysForContainer( Response.Status.INTERNAL_SERVER_ERROR); } KeysResponse keysResponse = - new KeysResponse(totalCount, keyMetadataMap.values()); + new KeysResponse(totalCount, keyMetadataMap.values(), lastKey); return Response.ok(keysResponse).build(); } @@ -334,7 +345,7 @@ public Response getMissingContainers( ) { List missingContainers = new ArrayList<>(); containerHealthSchemaManager.getUnhealthyContainers( - UnHealthyContainerStates.MISSING, 0, limit) + UnHealthyContainerStates.MISSING, 0, limit) .forEach(container -> { long containerID = container.getContainerId(); try { @@ -378,7 +389,7 @@ public Response getMissingContainers( public Response getUnhealthyContainers( @PathParam("state") String state, @DefaultValue(DEFAULT_FETCH_COUNT) @QueryParam(RECON_QUERY_LIMIT) - int limit, + int limit, @DefaultValue(DEFAULT_BATCH_NUMBER) @QueryParam(RECON_QUERY_BATCH_PARAM) int batchNum) { int offset = Math.max(((batchNum - 1) * limit), 0); @@ -428,7 +439,6 @@ public Response getUnhealthyContainers( * Return * {@link org.apache.hadoop.ozone.recon.api.types.UnhealthyContainerMetadata} * for all unhealthy containers. - * @param limit The limit of unhealthy containers to return. * @param batchNum The batch number (like "page number") of results to return. * Passing 1, will return records 1 to limit. 2 will return @@ -439,7 +449,7 @@ public Response getUnhealthyContainers( @Path("/unhealthy") public Response getUnhealthyContainers( @DefaultValue(DEFAULT_FETCH_COUNT) @QueryParam(RECON_QUERY_LIMIT) - int limit, + int limit, @DefaultValue(DEFAULT_BATCH_NUMBER) @QueryParam(RECON_QUERY_BATCH_PARAM) int batchNum) { return getUnhealthyContainers(null, limit, batchNum); @@ -514,6 +524,7 @@ public Response getSCMDeletedContainers( /** * Helper function to extract the blocks for a given container from a given * OM Key. + * * @param matchedKeys List of OM Key Info locations * @param containerID containerId. * @return List of blocks. @@ -698,7 +709,8 @@ public Response getContainerMisMatchInsights( } - /** This API retrieves set of deleted containers in SCM which are present + /** + * This API retrieves set of deleted containers in SCM which are present * in OM to find out list of keys mapped to such DELETED state containers. * * limit - limits the number of such SCM DELETED containers present in OM. diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/NSSummaryEndpoint.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/NSSummaryEndpoint.java index 5b104c461158..71040b9fdf64 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/NSSummaryEndpoint.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/NSSummaryEndpoint.java @@ -101,6 +101,8 @@ public Response getBasicInfo( * @param path request path * @param listFile show subpath/disk usage for each key * @param withReplica count actual DU with replication + * @param sortSubpaths determines whether to sort the subpaths by their sizes in descending order + * and returns the N largest subpaths based on the configuration value DISK_USAGE_TOP_RECORDS_LIMIT. * @return DU response * @throws IOException */ @@ -108,10 +110,9 @@ public Response getBasicInfo( @Path("/du") @SuppressWarnings("methodlength") public Response getDiskUsage(@QueryParam("path") String path, - @DefaultValue("false") - @QueryParam("files") boolean listFile, - @DefaultValue("false") - @QueryParam("replica") boolean withReplica) + @DefaultValue("false") @QueryParam("files") boolean listFile, + @DefaultValue("false") @QueryParam("replica") boolean withReplica, + @DefaultValue("true") @QueryParam("sortSubPaths") boolean sortSubpaths) throws IOException { if (path == null || path.length() == 0) { return Response.status(Response.Status.BAD_REQUEST).build(); @@ -127,8 +128,7 @@ public Response getDiskUsage(@QueryParam("path") String path, reconNamespaceSummaryManager, omMetadataManager, reconSCM, path); - duResponse = handler.getDuResponse( - listFile, withReplica); + duResponse = handler.getDuResponse(listFile, withReplica, sortSubpaths); return Response.ok(duResponse).build(); } diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/NodeEndpoint.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/NodeEndpoint.java index 33df0ca1bd5f..968bfbc46343 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/NodeEndpoint.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/NodeEndpoint.java @@ -168,6 +168,7 @@ private DatanodeStorageReport getStorageReport(DatanodeDetails datanode) { long capacity = nodeStat.getCapacity().get(); long used = nodeStat.getScmUsed().get(); long remaining = nodeStat.getRemaining().get(); - return new DatanodeStorageReport(capacity, used, remaining); + long committed = nodeStat.getCommitted().get(); + return new DatanodeStorageReport(capacity, used, remaining, committed); } } diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/BucketEntityHandler.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/BucketEntityHandler.java index 7ad961195ee7..00cd9617b5d3 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/BucketEntityHandler.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/BucketEntityHandler.java @@ -36,6 +36,9 @@ import java.util.List; import java.util.Set; +import static org.apache.hadoop.ozone.recon.ReconConstants.DISK_USAGE_TOP_RECORDS_LIMIT; +import static org.apache.hadoop.ozone.recon.ReconUtils.sortDiskUsageDescendingWithLimit; + /** * Class for handling bucket entity type. */ @@ -87,7 +90,7 @@ private BucketObjectDBInfo getBucketObjDbInfo(String[] names) @Override public DUResponse getDuResponse( - boolean listFile, boolean withReplica) + boolean listFile, boolean withReplica, boolean sortSubpaths) throws IOException { DUResponse duResponse = new DUResponse(); duResponse.setPath(getNormalizedPath()); @@ -142,7 +145,15 @@ public DUResponse getDuResponse( } duResponse.setCount(dirDUData.size()); duResponse.setSize(bucketDataSize); + + if (sortSubpaths) { + // Parallel sort directory/files DU data in descending order of size and returns the top N elements. + dirDUData = sortDiskUsageDescendingWithLimit(dirDUData, + DISK_USAGE_TOP_RECORDS_LIMIT); + } + duResponse.setDuData(dirDUData); + return duResponse; } diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/BucketHandler.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/BucketHandler.java index 09cbf4fe4e40..266caaa2d8e2 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/BucketHandler.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/BucketHandler.java @@ -17,9 +17,11 @@ */ package org.apache.hadoop.ozone.recon.api.handlers; +import org.apache.hadoop.hdds.conf.OzoneConfiguration; import org.apache.hadoop.hdds.scm.container.ContainerManager; import org.apache.hadoop.hdds.scm.server.OzoneStorageContainerManager; import org.apache.hadoop.hdds.utils.db.Table; +import org.apache.hadoop.ozone.om.OMConfigKeys; import org.apache.hadoop.ozone.om.helpers.OmDirectoryInfo; import org.apache.hadoop.ozone.om.helpers.OmKeyInfo; import org.apache.hadoop.ozone.om.helpers.OmBucketInfo; @@ -163,6 +165,8 @@ public static BucketHandler getBucketHandler( ReconOMMetadataManager omMetadataManager, OzoneStorageContainerManager reconSCM, OmBucketInfo bucketInfo) throws IOException { + // Check if enableFileSystemPaths flag is set to true. + boolean enableFileSystemPaths = isEnableFileSystemPaths(omMetadataManager); // If bucketInfo is null then entity type is UNKNOWN if (Objects.isNull(bucketInfo)) { @@ -172,15 +176,20 @@ public static BucketHandler getBucketHandler( .equals(BucketLayout.FILE_SYSTEM_OPTIMIZED)) { return new FSOBucketHandler(reconNamespaceSummaryManager, omMetadataManager, reconSCM, bucketInfo); - } else if (bucketInfo.getBucketLayout() - .equals(BucketLayout.LEGACY)) { - return new LegacyBucketHandler(reconNamespaceSummaryManager, - omMetadataManager, reconSCM, bucketInfo); + } else if (bucketInfo.getBucketLayout().equals(BucketLayout.LEGACY)) { + // Choose handler based on enableFileSystemPaths flag for legacy layout. + // If enableFileSystemPaths is false, then the legacy bucket is treated + // as an OBS bucket. + if (enableFileSystemPaths) { + return new LegacyBucketHandler(reconNamespaceSummaryManager, + omMetadataManager, reconSCM, bucketInfo); + } else { + return new OBSBucketHandler(reconNamespaceSummaryManager, + omMetadataManager, reconSCM, bucketInfo); + } } else if (bucketInfo.getBucketLayout() .equals(BucketLayout.OBJECT_STORE)) { - // TODO: HDDS-7810 Write a handler for object store bucket - // We can use LegacyBucketHandler for OBS bucket for now. - return new LegacyBucketHandler(reconNamespaceSummaryManager, + return new OBSBucketHandler(reconNamespaceSummaryManager, omMetadataManager, reconSCM, bucketInfo); } else { LOG.error("Unsupported bucket layout: " + @@ -190,6 +199,22 @@ public static BucketHandler getBucketHandler( } } + /** + * Determines whether FileSystemPaths are enabled for Legacy Buckets + * based on the Ozone configuration. + * + * @param ReconOMMetadataManager Instance + * @return True if FileSystemPaths are enabled, false otherwise. + */ + private static boolean isEnableFileSystemPaths(ReconOMMetadataManager omMetadataManager) { + OzoneConfiguration configuration = omMetadataManager.getOzoneConfiguration(); + if (configuration == null) { + configuration = new OzoneConfiguration(); + } + return configuration.getBoolean(OMConfigKeys.OZONE_OM_ENABLE_FILESYSTEM_PATHS, + OMConfigKeys.OZONE_OM_ENABLE_FILESYSTEM_PATHS_DEFAULT); + } + public static BucketHandler getBucketHandler( ReconNamespaceSummaryManager reconNamespaceSummaryManager, ReconOMMetadataManager omMetadataManager, diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/DirectoryEntityHandler.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/DirectoryEntityHandler.java index fc7022e2dab2..b535943081bd 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/DirectoryEntityHandler.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/DirectoryEntityHandler.java @@ -39,6 +39,9 @@ import java.util.List; import java.util.Set; +import static org.apache.hadoop.ozone.recon.ReconConstants.DISK_USAGE_TOP_RECORDS_LIMIT; +import static org.apache.hadoop.ozone.recon.ReconUtils.sortDiskUsageDescendingWithLimit; + /** * Class for handling directory entity type. */ @@ -80,7 +83,7 @@ private ObjectDBInfo getDirectoryObjDbInfo(String[] names) @Override public DUResponse getDuResponse( - boolean listFile, boolean withReplica) + boolean listFile, boolean withReplica, boolean sortSubPaths) throws IOException { DUResponse duResponse = new DUResponse(); duResponse.setPath(getNormalizedPath()); @@ -154,8 +157,14 @@ public DUResponse getDuResponse( } duResponse.setCount(subdirDUData.size()); duResponse.setSize(dirDataSize); - duResponse.setDuData(subdirDUData); + if (sortSubPaths) { + // Parallel sort subdirDUData in descending order of size and returns the top N elements. + subdirDUData = sortDiskUsageDescendingWithLimit(subdirDUData, + DISK_USAGE_TOP_RECORDS_LIMIT); + } + + duResponse.setDuData(subdirDUData); return duResponse; } diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/EntityHandler.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/EntityHandler.java index d12c7b6545ac..f2bcb58d3565 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/EntityHandler.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/EntityHandler.java @@ -19,6 +19,7 @@ import org.apache.hadoop.hdds.scm.server.OzoneStorageContainerManager; import org.apache.hadoop.ozone.OmUtils; +import org.apache.hadoop.ozone.om.helpers.BucketLayout; import org.apache.hadoop.ozone.recon.ReconConstants; import org.apache.hadoop.ozone.recon.api.types.NamespaceSummaryResponse; import org.apache.hadoop.ozone.recon.api.types.DUResponse; @@ -60,16 +61,25 @@ public EntityHandler( this.omMetadataManager = omMetadataManager; this.reconSCM = reconSCM; this.bucketHandler = bucketHandler; - normalizedPath = normalizePath(path); - names = parseRequestPath(normalizedPath); + // Defaulting to FILE_SYSTEM_OPTIMIZED if bucketHandler is null + BucketLayout layout = + (bucketHandler != null) ? bucketHandler.getBucketLayout() : + BucketLayout.FILE_SYSTEM_OPTIMIZED; + + // Normalize the path based on the determined layout + normalizedPath = normalizePath(path, layout); + + // Choose the parsing method based on the bucket layout + names = (layout == BucketLayout.OBJECT_STORE) ? + parseObjectStorePath(normalizedPath) : parseRequestPath(normalizedPath); } public abstract NamespaceSummaryResponse getSummaryResponse() throws IOException; public abstract DUResponse getDuResponse( - boolean listFile, boolean withReplica) + boolean listFile, boolean withReplica, boolean sort) throws IOException; public abstract QuotaUsageResponse getQuotaResponse() @@ -118,7 +128,8 @@ public static EntityHandler getEntityHandler( String path) throws IOException { BucketHandler bucketHandler; - String normalizedPath = normalizePath(path); + String normalizedPath = + normalizePath(path, BucketLayout.FILE_SYSTEM_OPTIMIZED); String[] names = parseRequestPath(normalizedPath); if (path.equals(OM_KEY_PREFIX)) { return EntityType.ROOT.create(reconNamespaceSummaryManager, @@ -156,23 +167,36 @@ public static EntityHandler getEntityHandler( String volName = names[0]; String bucketName = names[1]; - String keyName = BucketHandler.getKeyName(names); - + // Assuming getBucketHandler already validates volume and bucket existence bucketHandler = BucketHandler.getBucketHandler( - reconNamespaceSummaryManager, - omMetadataManager, reconSCM, - volName, bucketName); + reconNamespaceSummaryManager, omMetadataManager, reconSCM, volName, + bucketName); - // check if either volume or bucket doesn't exist - if (bucketHandler == null - || !omMetadataManager.volumeExists(volName) - || !bucketHandler.bucketExists(volName, bucketName)) { + if (bucketHandler == null) { return EntityType.UNKNOWN.create(reconNamespaceSummaryManager, - omMetadataManager, reconSCM, null, path); + omMetadataManager, reconSCM, null, path); + } + + // Directly handle path normalization and parsing based on the layout + if (bucketHandler.getBucketLayout() == BucketLayout.OBJECT_STORE) { + String[] parsedObjectLayoutPath = parseObjectStorePath( + normalizePath(path, bucketHandler.getBucketLayout())); + if (parsedObjectLayoutPath == null) { + return EntityType.UNKNOWN.create(reconNamespaceSummaryManager, + omMetadataManager, reconSCM, null, path); + } + // Use the key part directly from the parsed path + return bucketHandler.determineKeyPath(parsedObjectLayoutPath[2]) + .create(reconNamespaceSummaryManager, omMetadataManager, reconSCM, + bucketHandler, path); + } else { + // Use the existing names array for non-OBJECT_STORE layouts to derive + // the keyName + String keyName = BucketHandler.getKeyName(names); + return bucketHandler.determineKeyPath(keyName) + .create(reconNamespaceSummaryManager, omMetadataManager, reconSCM, + bucketHandler, path); } - return bucketHandler.determineKeyPath(keyName) - .create(reconNamespaceSummaryManager, - omMetadataManager, reconSCM, bucketHandler, path); } } @@ -256,7 +280,52 @@ public static String[] parseRequestPath(String path) { return names; } - private static String normalizePath(String path) { + /** + * Splits an object store path into volume, bucket, and key name components. + * + * This method parses a path of the format "/volumeName/bucketName/keyName", + * including paths with additional '/' characters within the key name. It's + * designed for object store paths where the first three '/' characters + * separate the root, volume and bucket names from the key name. + * + * @param path The object store path to parse, starting with a slash. + * @return A String array with three elements: volume name, bucket name, and + * key name, or {null} if the path format is invalid. + */ + public static String[] parseObjectStorePath(String path) { + // Removing the leading slash for correct splitting + path = path.substring(1); + + // Splitting the modified path by "/", limiting to 3 parts + String[] parts = path.split("/", 3); + + // Checking if we correctly obtained 3 parts after removing the leading slash + if (parts.length <= 3) { + return parts; + } else { + return null; + } + } + + /** + * Normalizes a given path based on the specified bucket layout. + * + * This method adjusts the path according to the bucket layout. + * For {OBJECT_STORE Layout}, it normalizes the path up to the bucket level + * using OmUtils.normalizePathUptoBucket. For other layouts, it + * normalizes the entire path, including the key, using + * OmUtils.normalizeKey, and does not preserve any trailing slashes. + * The normalized path will always be prefixed with OM_KEY_PREFIX to ensure it + * is consistent with the expected format for object storage paths in Ozone. + * + * @param path + * @param bucketLayout + * @return A normalized path + */ + private static String normalizePath(String path, BucketLayout bucketLayout) { + if (bucketLayout == BucketLayout.OBJECT_STORE) { + return OM_KEY_PREFIX + OmUtils.normalizePathUptoBucket(path); + } return OM_KEY_PREFIX + OmUtils.normalizeKey(path, false); } } diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/FSOBucketHandler.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/FSOBucketHandler.java index 26cda6442d4e..8a1c5babe75e 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/FSOBucketHandler.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/FSOBucketHandler.java @@ -42,7 +42,7 @@ import static org.apache.hadoop.ozone.OzoneConsts.OM_KEY_PREFIX; /** - * Class for handling FSO buckets. + * Class for handling FSO buckets NameSpaceSummaries. */ public class FSOBucketHandler extends BucketHandler { private static final Logger LOG = diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/KeyEntityHandler.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/KeyEntityHandler.java index a687bf3d0bdd..8ea26fd2846e 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/KeyEntityHandler.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/KeyEntityHandler.java @@ -71,7 +71,7 @@ private ObjectDBInfo getKeyDbObjectInfo(String[] names) @Override public DUResponse getDuResponse( - boolean listFile, boolean withReplica) + boolean listFile, boolean withReplica, boolean sort) throws IOException { DUResponse duResponse = new DUResponse(); duResponse.setPath(getNormalizedPath()); diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/LegacyBucketHandler.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/LegacyBucketHandler.java index 3dd1ddbdabb9..09f1c5bc7454 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/LegacyBucketHandler.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/LegacyBucketHandler.java @@ -41,7 +41,7 @@ import static org.apache.hadoop.ozone.OzoneConsts.OM_KEY_PREFIX; /** - * Class for handling Legacy buckets. + * Class for handling Legacy buckets NameSpaceSummaries. */ public class LegacyBucketHandler extends BucketHandler { diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/OBSBucketHandler.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/OBSBucketHandler.java new file mode 100644 index 000000000000..024eec989a10 --- /dev/null +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/OBSBucketHandler.java @@ -0,0 +1,268 @@ +/* + * 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.ozone.recon.api.handlers; + + +import org.apache.hadoop.hdds.scm.server.OzoneStorageContainerManager; +import org.apache.hadoop.hdds.utils.db.Table; +import org.apache.hadoop.hdds.utils.db.TableIterator; +import org.apache.hadoop.ozone.om.helpers.BucketLayout; +import org.apache.hadoop.ozone.om.helpers.OmBucketInfo; +import org.apache.hadoop.ozone.om.helpers.OmDirectoryInfo; +import org.apache.hadoop.ozone.om.helpers.OmKeyInfo; +import org.apache.hadoop.ozone.recon.api.types.DUResponse; +import org.apache.hadoop.ozone.recon.api.types.EntityType; +import org.apache.hadoop.ozone.recon.api.types.NSSummary; +import org.apache.hadoop.ozone.recon.recovery.ReconOMMetadataManager; +import org.apache.hadoop.ozone.recon.spi.ReconNamespaceSummaryManager; + +import java.io.IOException; +import java.util.List; + +import static org.apache.hadoop.ozone.OzoneConsts.OM_KEY_PREFIX; + +/** + * Class for handling OBS buckets NameSpaceSummaries. + */ +public class OBSBucketHandler extends BucketHandler { + + private final String vol; + private final String bucket; + private final OmBucketInfo omBucketInfo; + + public OBSBucketHandler( + ReconNamespaceSummaryManager reconNamespaceSummaryManager, + ReconOMMetadataManager omMetadataManager, + OzoneStorageContainerManager reconSCM, + OmBucketInfo bucketInfo) { + super(reconNamespaceSummaryManager, omMetadataManager, + reconSCM); + this.omBucketInfo = bucketInfo; + this.vol = omBucketInfo.getVolumeName(); + this.bucket = omBucketInfo.getBucketName(); + } + + /** + * Helper function to check if a path is a key, or invalid. + * + * @param keyName key name + * @return KEY, or UNKNOWN + * @throws IOException + */ + @Override + public EntityType determineKeyPath(String keyName) throws IOException { + String key = OM_KEY_PREFIX + vol + + OM_KEY_PREFIX + bucket + + OM_KEY_PREFIX + keyName; + + Table keyTable = getKeyTable(); + + try ( + TableIterator> + iterator = keyTable.iterator()) { + iterator.seek(key); + if (iterator.hasNext()) { + Table.KeyValue kv = iterator.next(); + String dbKey = kv.getKey(); + if (dbKey.equals(key)) { + return EntityType.KEY; + } + } + } + return EntityType.UNKNOWN; + } + + /** + * This method handles disk usage of direct keys. + * + * @param parentId The identifier for the parent bucket. + * @param withReplica if withReplica is enabled, set sizeWithReplica + * for each direct key's DU + * @param listFile if listFile is enabled, append key DU as a children + * keys + * @param duData the current DU data + * @param normalizedPath the normalized path request + * @return the total DU of all direct keys + * @throws IOException IOE + */ + @Override + public long handleDirectKeys(long parentId, boolean withReplica, + boolean listFile, + List duData, + String normalizedPath) throws IOException { + + NSSummary nsSummary = getReconNamespaceSummaryManager() + .getNSSummary(parentId); + // Handle the case of an empty bucket. + if (nsSummary == null) { + return 0; + } + + Table keyTable = getKeyTable(); + long keyDataSizeWithReplica = 0L; + + try ( + TableIterator> + iterator = keyTable.iterator()) { + + String seekPrefix = OM_KEY_PREFIX + + vol + + OM_KEY_PREFIX + + bucket + + OM_KEY_PREFIX; + + iterator.seek(seekPrefix); + + while (iterator.hasNext()) { + // KeyName : OmKeyInfo-Object + Table.KeyValue kv = iterator.next(); + String dbKey = kv.getKey(); + + // Exit loop if the key doesn't match the seekPrefix. + if (!dbKey.startsWith(seekPrefix)) { + break; + } + + OmKeyInfo keyInfo = kv.getValue(); + if (keyInfo != null) { + DUResponse.DiskUsage diskUsage = new DUResponse.DiskUsage(); + String objectName = keyInfo.getKeyName(); + diskUsage.setSubpath(objectName); + diskUsage.setKey(true); + diskUsage.setSize(keyInfo.getDataSize()); + + if (withReplica) { + long keyDU = keyInfo.getReplicatedSize(); + keyDataSizeWithReplica += keyDU; + diskUsage.setSizeWithReplica(keyDU); + } + // List all the keys for the OBS bucket if requested. + if (listFile) { + duData.add(diskUsage); + } + } + } + } + + return keyDataSizeWithReplica; + } + + /** + * Calculates the total disk usage (DU) for an Object Store Bucket (OBS) by + * summing the sizes of all keys contained within the bucket. + * Since OBS buckets operate on a flat hierarchy, this method iterates through + * all the keys in the bucket without the need to traverse directories. + * + * @param parentId The identifier for the parent bucket. + * @return The total disk usage of all keys within the specified OBS bucket. + * @throws IOException + */ + @Override + public long calculateDUUnderObject(long parentId) throws IOException { + // Initialize the total disk usage variable. + long totalDU = 0L; + + // Access the key table for the bucket. + Table keyTable = getKeyTable(); + + try ( + TableIterator> + iterator = keyTable.iterator()) { + // Construct the seek prefix to filter keys under this bucket. + String seekPrefix = + OM_KEY_PREFIX + vol + OM_KEY_PREFIX + bucket + OM_KEY_PREFIX; + iterator.seek(seekPrefix); + + // Iterate over keys in the bucket. + while (iterator.hasNext()) { + Table.KeyValue kv = iterator.next(); + String keyName = kv.getKey(); + + // Break the loop if the current key does not start with the seekPrefix. + if (!keyName.startsWith(seekPrefix)) { + break; + } + + // Sum the size of each key to the total disk usage. + OmKeyInfo keyInfo = kv.getValue(); + if (keyInfo != null) { + totalDU += keyInfo.getDataSize(); + } + } + } + + // Return the total disk usage of all keys in the bucket. + return totalDU; + } + + /** + * Object stores do not support directories. + * + * @throws UnsupportedOperationException + */ + @Override + public long getDirObjectId(String[] names) + throws UnsupportedOperationException { + throw new UnsupportedOperationException( + "Object stores do not support directories."); + } + + /** + * Object stores do not support directories. + * + * @throws UnsupportedOperationException + */ + @Override + public long getDirObjectId(String[] names, int cutoff) + throws UnsupportedOperationException { + throw new UnsupportedOperationException( + "Object stores do not support directories."); + } + + /** + * Returns the keyInfo object from the KEY table. + * @return OmKeyInfo + */ + @Override + public OmKeyInfo getKeyInfo(String[] names) throws IOException { + String ozoneKey = OM_KEY_PREFIX; + ozoneKey += String.join(OM_KEY_PREFIX, names); + + return getKeyTable().getSkipCache(ozoneKey); + } + + /** + * Object stores do not support directories. + * + * @throws UnsupportedOperationException + */ + @Override + public OmDirectoryInfo getDirInfo(String[] names) throws IOException { + throw new UnsupportedOperationException( + "Object stores do not support directories."); + } + + public Table getKeyTable() { + return getOmMetadataManager().getKeyTable(getBucketLayout()); + } + + public BucketLayout getBucketLayout() { + return BucketLayout.OBJECT_STORE; + } + +} diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/RootEntityHandler.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/RootEntityHandler.java index fd0e58f191af..b67703257ac1 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/RootEntityHandler.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/RootEntityHandler.java @@ -39,6 +39,9 @@ import java.util.ArrayList; import java.util.List; +import static org.apache.hadoop.ozone.recon.ReconConstants.DISK_USAGE_TOP_RECORDS_LIMIT; +import static org.apache.hadoop.ozone.recon.ReconUtils.sortDiskUsageDescendingWithLimit; + /** * Class for handling root entity type. */ @@ -88,7 +91,7 @@ private ObjectDBInfo getPrefixObjDbInfo() @Override public DUResponse getDuResponse( - boolean listFile, boolean withReplica) + boolean listFile, boolean withReplica, boolean sortSubPaths) throws IOException { DUResponse duResponse = new DUResponse(); duResponse.setPath(getNormalizedPath()); @@ -137,6 +140,13 @@ public DUResponse getDuResponse( duResponse.setSizeWithReplica(totalDataSizeWithReplica); } duResponse.setSize(totalDataSize); + + if (sortSubPaths) { + // Parallel sort volumeDuData in descending order of size and returns the top N elements. + volumeDuData = sortDiskUsageDescendingWithLimit(volumeDuData, + DISK_USAGE_TOP_RECORDS_LIMIT); + } + duResponse.setDuData(volumeDuData); return duResponse; @@ -148,7 +158,8 @@ public QuotaUsageResponse getQuotaResponse() QuotaUsageResponse quotaUsageResponse = new QuotaUsageResponse(); SCMNodeStat stats = getReconSCM().getScmNodeManager().getStats(); long quotaInBytes = stats.getCapacity().get(); - long quotaUsedInBytes = getDuResponse(true, true).getSizeWithReplica(); + long quotaUsedInBytes = + getDuResponse(true, true, false).getSizeWithReplica(); quotaUsageResponse.setQuota(quotaInBytes); quotaUsageResponse.setQuotaUsed(quotaUsedInBytes); return quotaUsageResponse; diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/UnknownEntityHandler.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/UnknownEntityHandler.java index b5a5bd9a0be9..ab61ec38e8bf 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/UnknownEntityHandler.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/UnknownEntityHandler.java @@ -51,7 +51,7 @@ public NamespaceSummaryResponse getSummaryResponse() @Override public DUResponse getDuResponse( - boolean listFile, boolean withReplica) + boolean listFile, boolean withReplica, boolean sort) throws IOException { DUResponse duResponse = new DUResponse(); duResponse.setStatus(ResponseStatus.PATH_NOT_FOUND); diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/VolumeEntityHandler.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/VolumeEntityHandler.java index fae508a99c9d..2ca9c352ce77 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/VolumeEntityHandler.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/handlers/VolumeEntityHandler.java @@ -36,6 +36,10 @@ import java.util.ArrayList; import java.util.List; + +import static org.apache.hadoop.ozone.recon.ReconConstants.DISK_USAGE_TOP_RECORDS_LIMIT; +import static org.apache.hadoop.ozone.recon.ReconUtils.sortDiskUsageDescendingWithLimit; + /** * Class for handling volume entity type. */ @@ -92,7 +96,7 @@ private VolumeObjectDBInfo getVolumeObjDbInfo(String[] names) @Override public DUResponse getDuResponse( - boolean listFile, boolean withReplica) + boolean listFile, boolean withReplica, boolean sortSubPaths) throws IOException { DUResponse duResponse = new DUResponse(); duResponse.setPath(getNormalizedPath()); @@ -131,6 +135,13 @@ public DUResponse getDuResponse( duResponse.setSizeWithReplica(volDataSizeWithReplica); } duResponse.setSize(volDataSize); + + if (sortSubPaths) { + // Parallel sort bucketDuData in descending order of size and returns the top N elements. + bucketDuData = sortDiskUsageDescendingWithLimit(bucketDuData, + DISK_USAGE_TOP_RECORDS_LIMIT); + } + duResponse.setDuData(bucketDuData); return duResponse; } diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/types/DatanodeStorageReport.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/types/DatanodeStorageReport.java index d3fbb598c1b2..43a20317a29e 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/types/DatanodeStorageReport.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/types/DatanodeStorageReport.java @@ -24,11 +24,14 @@ public class DatanodeStorageReport { private long capacity; private long used; private long remaining; + private long committed; - public DatanodeStorageReport(long capacity, long used, long remaining) { + public DatanodeStorageReport(long capacity, long used, long remaining, + long committed) { this.capacity = capacity; this.used = used; this.remaining = remaining; + this.committed = committed; } public long getCapacity() { @@ -42,4 +45,8 @@ public long getUsed() { public long getRemaining() { return remaining; } + + public long getCommitted() { + return committed; + } } diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/types/KeyMetadata.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/types/KeyMetadata.java index c48e21d90f90..5094f47c24c2 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/types/KeyMetadata.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/types/KeyMetadata.java @@ -45,6 +45,9 @@ public class KeyMetadata { @XmlElement(name = "Key") private String key; + @XmlElement(name = "CompletePath") + private String completePath; + @XmlElement(name = "DataSize") private long dataSize; @@ -126,6 +129,14 @@ public void setBlockIds(Map> blockIds) { this.blockIds = blockIds; } + public String getCompletePath() { + return completePath; + } + + public void setCompletePath(String completePath) { + this.completePath = completePath; + } + /** * Class to hold ContainerID and BlockID. */ diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/types/KeysResponse.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/types/KeysResponse.java index 5b05975623c1..c09d28718e8b 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/types/KeysResponse.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/types/KeysResponse.java @@ -36,9 +36,13 @@ public class KeysResponse { @JsonProperty("keys") private Collection keys; - public KeysResponse(long totalCount, Collection keys) { + @JsonProperty("lastKey") + private String lastKey; + + public KeysResponse(long totalCount, Collection keys, String lastKey) { this.totalCount = totalCount; this.keys = keys; + this.lastKey = lastKey; } public long getTotalCount() { @@ -48,4 +52,7 @@ public long getTotalCount() { public Collection getKeys() { return keys; } + public String getLastKey() { + return lastKey; + } } diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/types/NSSummary.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/types/NSSummary.java index c0f93aebe97d..0f774f01bf48 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/types/NSSummary.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/types/NSSummary.java @@ -36,22 +36,25 @@ public class NSSummary { private int[] fileSizeBucket; private Set childDir; private String dirName; + private long parentId = 0; public NSSummary() { this(0, 0L, new int[ReconConstants.NUM_OF_FILE_SIZE_BINS], - new HashSet<>(), ""); + new HashSet<>(), "", 0); } public NSSummary(int numOfFiles, long sizeOfFiles, int[] bucket, Set childDir, - String dirName) { + String dirName, + long parentId) { this.numOfFiles = numOfFiles; this.sizeOfFiles = sizeOfFiles; setFileSizeBucket(bucket); this.childDir = childDir; this.dirName = dirName; + this.parentId = parentId; } public int getNumOfFiles() { @@ -107,4 +110,12 @@ public void removeChildDir(long childId) { this.childDir.remove(childId); } } + + public long getParentId() { + return parentId; + } + + public void setParentId(long parentId) { + this.parentId = parentId; + } } diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/codec/NSSummaryCodec.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/codec/NSSummaryCodec.java index 09e0b2587934..f3b273451a2d 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/codec/NSSummaryCodec.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/codec/NSSummaryCodec.java @@ -65,9 +65,10 @@ public byte[] toPersistedFormat(NSSummary object) throws IOException { int stringLen = dirName.getBytes(StandardCharsets.UTF_8).length; int numOfChildDirs = childDirs.size(); final int resSize = NUM_OF_INTS * Integer.BYTES - + (numOfChildDirs + 1) * Long.BYTES // 1 long field + list size + + (numOfChildDirs + 1) * Long.BYTES // 1 long field for parentId + list size + Short.BYTES // 2 dummy shorts to track length - + stringLen; // directory name length + + stringLen // directory name length + + Long.BYTES; // Added space for parentId serialization ByteArrayOutputStream out = new ByteArrayOutputStream(resSize); out.write(integerCodec.toPersistedFormat(object.getNumOfFiles())); @@ -84,6 +85,8 @@ public byte[] toPersistedFormat(NSSummary object) throws IOException { } out.write(integerCodec.toPersistedFormat(stringLen)); out.write(stringCodec.toPersistedFormat(dirName)); + out.write(longCodec.toPersistedFormat(object.getParentId())); + return out.toByteArray(); } @@ -117,6 +120,15 @@ public NSSummary fromPersistedFormat(byte[] rawData) throws IOException { assert (bytesRead == strLen); String dirName = stringCodec.fromPersistedFormat(buffer); res.setDirName(dirName); + + // Check if there is enough data available to read the parentId + if (in.available() >= Long.BYTES) { + long parentId = in.readLong(); + res.setParentId(parentId); + } else { + // Set default parentId to -1 indicating it's from old format + res.setParentId(-1); + } return res; } @@ -128,6 +140,7 @@ public NSSummary copyObject(NSSummary object) { copy.setFileSizeBucket(object.getFileSizeBucket()); copy.setChildDir(object.getChildDir()); copy.setDirName(object.getDirName()); + copy.setParentId(object.getParentId()); return copy; } } diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/heatmap/HeatMapUtil.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/heatmap/HeatMapUtil.java index b885ea5adae4..c1c4ba44d18c 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/heatmap/HeatMapUtil.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/heatmap/HeatMapUtil.java @@ -71,7 +71,7 @@ private long getEntitySize(String path) throws IOException { EntityHandler.getEntityHandler(reconNamespaceSummaryManager, omMetadataManager, reconSCM, path); if (null != entityHandler) { - DUResponse duResponse = entityHandler.getDuResponse(false, false); + DUResponse duResponse = entityHandler.getDuResponse(false, false, false); if (null != duResponse && duResponse.getStatus() == ResponseStatus.OK) { return duResponse.getSize(); } diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/recovery/ReconOMMetadataManager.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/recovery/ReconOMMetadataManager.java index 2040b7b343d9..1fc114eabd75 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/recovery/ReconOMMetadataManager.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/recovery/ReconOMMetadataManager.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.util.List; +import org.apache.hadoop.hdds.conf.OzoneConfiguration; import org.apache.hadoop.ozone.om.OMMetadataManager; import org.apache.hadoop.ozone.om.helpers.OmBucketInfo; import org.apache.hadoop.ozone.om.helpers.OmVolumeArgs; @@ -105,4 +106,11 @@ List listBucketsUnderVolume(String volumeName, */ List listBucketsUnderVolume( String volumeName) throws IOException; + + /** + * Return the OzoneConfiguration instance used by Recon. + * @return + */ + OzoneConfiguration getOzoneConfiguration(); + } diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/recovery/ReconOmMetadataManagerImpl.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/recovery/ReconOmMetadataManagerImpl.java index ad0526363df0..4b041f6511f6 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/recovery/ReconOmMetadataManagerImpl.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/recovery/ReconOmMetadataManagerImpl.java @@ -291,6 +291,11 @@ public List listBucketsUnderVolume(final String volumeName) Integer.MAX_VALUE); } + @Override + public OzoneConfiguration getOzoneConfiguration() { + return ozoneConfiguration; + } + private List listAllBuckets(final int maxNumberOfBuckets) throws IOException { List result = new ArrayList<>(); diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconContainerManager.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconContainerManager.java index d1d8373a29fd..3dde78a0fd64 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconContainerManager.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconContainerManager.java @@ -456,4 +456,8 @@ public Map getPipelineToOpenContainer() { return pipelineToOpenContainer; } + @VisibleForTesting + public StorageContainerServiceProvider getScmClient() { + return scmClient; + } } diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconContainerReportQueue.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconContainerReportQueue.java new file mode 100644 index 000000000000..8d5f92eda4ca --- /dev/null +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconContainerReportQueue.java @@ -0,0 +1,47 @@ +/* + * 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.ozone.recon.scm; + +import org.apache.hadoop.hdds.scm.server.ContainerReportQueue; +import org.apache.hadoop.hdds.scm.server.SCMDatanodeHeartbeatDispatcher; +import org.apache.hadoop.hdds.scm.server.SCMDatanodeHeartbeatDispatcher.ContainerReport; + +import java.util.List; + +/** + * Customized queue to handle multiple ICR report together. + */ +public class ReconContainerReportQueue extends ContainerReportQueue { + + public ReconContainerReportQueue(int queueSize) { + super(queueSize); + } + + @Override + protected boolean mergeIcr(ContainerReport val, + List dataList) { + if (!dataList.isEmpty()) { + if (SCMDatanodeHeartbeatDispatcher.ContainerReportType.ICR + == dataList.get(dataList.size() - 1).getType()) { + dataList.get(dataList.size() - 1).mergeReport(val); + return true; + } + } + return false; + } +} diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconIncrementalContainerReportHandler.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconIncrementalContainerReportHandler.java index 18d995d053aa..1f2b1d5cf249 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconIncrementalContainerReportHandler.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconIncrementalContainerReportHandler.java @@ -24,8 +24,8 @@ import org.apache.hadoop.hdds.protocol.DatanodeDetails; import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.ContainerReplicaProto; import org.apache.hadoop.hdds.scm.container.ContainerID; +import org.apache.hadoop.hdds.scm.container.ContainerInfo; import org.apache.hadoop.hdds.scm.container.ContainerManager; -import org.apache.hadoop.hdds.scm.container.ContainerNotFoundException; import org.apache.hadoop.hdds.scm.container.IncrementalContainerReportHandler; import org.apache.hadoop.hdds.scm.ha.SCMContext; import org.apache.hadoop.hdds.scm.node.NodeManager; @@ -69,24 +69,33 @@ public void onMessage(final IncrementalContainerReportFromDatanode report, ReconContainerManager containerManager = (ReconContainerManager) getContainerManager(); + try { + containerManager.checkAndAddNewContainerBatch( + report.getReport().getReportList()); + } catch (Exception ioEx) { + LOG.error("Exception while checking and adding new container.", ioEx); + return; + } boolean success = true; for (ContainerReplicaProto replicaProto : report.getReport().getReportList()) { + ContainerID id = ContainerID.valueOf(replicaProto.getContainerID()); + ContainerInfo container = null; try { - final ContainerID id = ContainerID.valueOf( - replicaProto.getContainerID()); try { - containerManager.checkAndAddNewContainer(id, replicaProto.getState(), - report.getDatanodeDetails()); - } catch (Exception ioEx) { - LOG.error("Exception while checking and adding new container.", ioEx); - return; + container = getContainerManager().getContainer(id); + // Ensure we reuse the same ContainerID instance in containerInfo + id = container.containerID(); + } finally { + if (replicaProto.getState().equals( + ContainerReplicaProto.State.DELETED)) { + getNodeManager().removeContainer(dd, id); + } else { + getNodeManager().addContainer(dd, id); + } } - getNodeManager().addContainer(dd, id); processContainerReplica(dd, replicaProto, publisher); - } catch (ContainerNotFoundException e) { - success = false; - LOG.warn("Container {} not found!", replicaProto.getContainerID()); + success = true; } catch (NodeNotFoundException ex) { success = false; LOG.error("Received ICR from unknown datanode {}.", diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconStorageContainerManagerFacade.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconStorageContainerManagerFacade.java index 464ec1a5ee85..556c6194192f 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconStorageContainerManagerFacade.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconStorageContainerManagerFacade.java @@ -99,11 +99,21 @@ import org.apache.hadoop.ozone.recon.tasks.ContainerSizeCountTask; import org.apache.hadoop.ozone.recon.tasks.ReconTaskConfig; import com.google.inject.Inject; + import static org.apache.hadoop.hdds.recon.ReconConfigKeys.RECON_SCM_CONFIG_PREFIX; import static org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_EVENT_REPORT_EXEC_WAIT_THRESHOLD_DEFAULT; import static org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_EVENT_REPORT_QUEUE_WAIT_THRESHOLD_DEFAULT; import static org.apache.hadoop.hdds.scm.server.StorageContainerManager.buildRpcServerStartMessage; +import static org.apache.hadoop.ozone.OzoneConfigKeys.HDDS_SCM_CLIENT_FAILOVER_MAX_RETRY; +import static org.apache.hadoop.ozone.OzoneConfigKeys.HDDS_SCM_CLIENT_MAX_RETRY_TIMEOUT; +import static org.apache.hadoop.ozone.OzoneConfigKeys.HDDS_SCM_CLIENT_RPC_TIME_OUT; import static org.apache.hadoop.ozone.OzoneConsts.OZONE_URI_DELIMITER; +import static org.apache.hadoop.ozone.recon.ReconServerConfigKeys.OZONE_RECON_SCM_CLIENT_FAILOVER_MAX_RETRY_DEFAULT; +import static org.apache.hadoop.ozone.recon.ReconServerConfigKeys.OZONE_RECON_SCM_CLIENT_FAILOVER_MAX_RETRY_KEY; +import static org.apache.hadoop.ozone.recon.ReconServerConfigKeys.OZONE_RECON_SCM_CLIENT_MAX_RETRY_TIMEOUT_DEFAULT; +import static org.apache.hadoop.ozone.recon.ReconServerConfigKeys.OZONE_RECON_SCM_CLIENT_MAX_RETRY_TIMEOUT_KEY; +import static org.apache.hadoop.ozone.recon.ReconServerConfigKeys.OZONE_RECON_SCM_CLIENT_RPC_TIME_OUT_DEFAULT; +import static org.apache.hadoop.ozone.recon.ReconServerConfigKeys.OZONE_RECON_SCM_CLIENT_RPC_TIME_OUT_KEY; import static org.apache.hadoop.ozone.recon.ReconServerConfigKeys.OZONE_RECON_SCM_SNAPSHOT_TASK_INITIAL_DELAY; import static org.apache.hadoop.ozone.recon.ReconServerConfigKeys.OZONE_RECON_SCM_SNAPSHOT_TASK_INITIAL_DELAY_DEFAULT; import static org.apache.hadoop.ozone.recon.ReconServerConfigKeys.OZONE_RECON_SCM_SNAPSHOT_TASK_INTERVAL_DEFAULT; @@ -182,6 +192,23 @@ public ReconStorageContainerManagerFacade(OzoneConfiguration conf, .setSCM(this) .build(); this.ozoneConfiguration = getReconScmConfiguration(conf); + long scmClientRPCTimeOut = conf.getTimeDuration( + OZONE_RECON_SCM_CLIENT_RPC_TIME_OUT_KEY, + OZONE_RECON_SCM_CLIENT_RPC_TIME_OUT_DEFAULT, + TimeUnit.MILLISECONDS); + long scmClientMaxRetryTimeOut = conf.getTimeDuration( + OZONE_RECON_SCM_CLIENT_MAX_RETRY_TIMEOUT_KEY, + OZONE_RECON_SCM_CLIENT_MAX_RETRY_TIMEOUT_DEFAULT, + TimeUnit.MILLISECONDS); + int scmClientFailOverMaxRetryCount = conf.getInt( + OZONE_RECON_SCM_CLIENT_FAILOVER_MAX_RETRY_KEY, + OZONE_RECON_SCM_CLIENT_FAILOVER_MAX_RETRY_DEFAULT); + + conf.setLong(HDDS_SCM_CLIENT_RPC_TIME_OUT, scmClientRPCTimeOut); + conf.setLong(HDDS_SCM_CLIENT_MAX_RETRY_TIMEOUT, scmClientMaxRetryTimeOut); + conf.setLong(HDDS_SCM_CLIENT_FAILOVER_MAX_RETRY, + scmClientFailOverMaxRetryCount); + this.scmStorageConfig = new ReconStorageConfig(conf, reconUtils); this.clusterMap = new NetworkTopologyImpl(conf); this.dbStore = DBStoreBuilder @@ -283,7 +310,7 @@ public ReconStorageContainerManagerFacade(OzoneConfiguration conf, ScmUtils.getContainerReportConfPrefix() + ".execute.wait.threshold", OZONE_SCM_EVENT_REPORT_EXEC_WAIT_THRESHOLD_DEFAULT); List> queues - = ScmUtils.initContainerReportQueue(ozoneConfiguration); + = ReconUtils.initContainerReportQueue(ozoneConfiguration); List executors = FixedThreadPoolWithAffinityExecutor.initializeExecutorPool( threadNamePrefix, queues); diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/spi/ReconNamespaceSummaryManager.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/spi/ReconNamespaceSummaryManager.java index 6cb93e7134a2..ea0ff6ed5df4 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/spi/ReconNamespaceSummaryManager.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/spi/ReconNamespaceSummaryManager.java @@ -21,6 +21,7 @@ import org.apache.hadoop.hdds.annotation.InterfaceStability; import org.apache.hadoop.hdds.utils.db.BatchOperation; import org.apache.hadoop.hdds.utils.db.RDBBatchOperation; +import org.apache.hadoop.ozone.om.OMMetadataManager; import org.apache.hadoop.ozone.recon.api.types.NSSummary; import java.io.IOException; @@ -45,4 +46,6 @@ void batchStoreNSSummaries(BatchOperation batch, long objectId, void commitBatchOperation(RDBBatchOperation rdbBatchOperation) throws IOException; + + void rebuildNSSummaryTree(OMMetadataManager omMetadataManager); } diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/spi/impl/ReconNamespaceSummaryManagerImpl.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/spi/impl/ReconNamespaceSummaryManagerImpl.java index 42a30095f315..9167854a8263 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/spi/impl/ReconNamespaceSummaryManagerImpl.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/spi/impl/ReconNamespaceSummaryManagerImpl.java @@ -22,8 +22,11 @@ import org.apache.hadoop.hdds.utils.db.DBStore; import org.apache.hadoop.hdds.utils.db.RDBBatchOperation; import org.apache.hadoop.hdds.utils.db.Table; +import org.apache.hadoop.ozone.om.OMMetadataManager; import org.apache.hadoop.ozone.recon.api.types.NSSummary; import org.apache.hadoop.ozone.recon.spi.ReconNamespaceSummaryManager; +import org.apache.hadoop.ozone.recon.tasks.NSSummaryTask; + import static org.apache.hadoop.ozone.recon.spi.impl.ReconDBProvider.truncateTable; import javax.inject.Inject; @@ -39,12 +42,14 @@ public class ReconNamespaceSummaryManagerImpl private Table nsSummaryTable; private DBStore namespaceDbStore; + private NSSummaryTask nsSummaryTask; @Inject - public ReconNamespaceSummaryManagerImpl(ReconDBProvider reconDBProvider) + public ReconNamespaceSummaryManagerImpl(ReconDBProvider reconDBProvider, NSSummaryTask nsSummaryTask) throws IOException { namespaceDbStore = reconDBProvider.getDbStore(); this.nsSummaryTable = NAMESPACE_SUMMARY.getTable(namespaceDbStore); + this.nsSummaryTask = nsSummaryTask; } @Override @@ -81,6 +86,11 @@ public void commitBatchOperation(RDBBatchOperation rdbBatchOperation) this.namespaceDbStore.commitBatchOperation(rdbBatchOperation); } + @Override + public void rebuildNSSummaryTree(OMMetadataManager omMetadataManager) { + nsSummaryTask.reprocess(omMetadataManager); + } + public Table getNSSummaryTable() { return nsSummaryTable; } diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/tasks/NSSummaryTask.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/tasks/NSSummaryTask.java index 42356191c501..30fdb7c1292e 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/tasks/NSSummaryTask.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/tasks/NSSummaryTask.java @@ -38,6 +38,7 @@ import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; /** * Task to query data from OMDB and write into Recon RocksDB. @@ -62,12 +63,13 @@ */ public class NSSummaryTask implements ReconOmTask { private static final Logger LOG = - LoggerFactory.getLogger(NSSummaryTask.class); + LoggerFactory.getLogger(NSSummaryTask.class); private final ReconNamespaceSummaryManager reconNamespaceSummaryManager; private final ReconOMMetadataManager reconOMMetadataManager; private final NSSummaryTaskWithFSO nsSummaryTaskWithFSO; private final NSSummaryTaskWithLegacy nsSummaryTaskWithLegacy; + private final NSSummaryTaskWithOBS nsSummaryTaskWithOBS; private final OzoneConfiguration ozoneConfiguration; @Inject @@ -86,6 +88,9 @@ public NSSummaryTask(ReconNamespaceSummaryManager this.nsSummaryTaskWithLegacy = new NSSummaryTaskWithLegacy( reconNamespaceSummaryManager, reconOMMetadataManager, ozoneConfiguration); + this.nsSummaryTaskWithOBS = new NSSummaryTaskWithOBS( + reconNamespaceSummaryManager, + reconOMMetadataManager, ozoneConfiguration); } @Override @@ -95,20 +100,28 @@ public String getTaskName() { @Override public Pair process(OMUpdateEventBatch events) { - boolean success; - success = nsSummaryTaskWithFSO.processWithFSO(events); - if (success) { - success = nsSummaryTaskWithLegacy.processWithLegacy(events); - } else { + boolean success = nsSummaryTaskWithFSO.processWithFSO(events); + if (!success) { LOG.error("processWithFSO failed."); } + success = nsSummaryTaskWithLegacy.processWithLegacy(events); + if (!success) { + LOG.error("processWithLegacy failed."); + } + success = nsSummaryTaskWithOBS.processWithOBS(events); + if (!success) { + LOG.error("processWithOBS failed."); + } return new ImmutablePair<>(getTaskName(), success); } @Override public Pair reprocess(OMMetadataManager omMetadataManager) { + // Initialize a list of tasks to run in parallel Collection> tasks = new ArrayList<>(); + long startTime = System.nanoTime(); // Record start time + try { // reinit Recon RocksDB's namespace CF. reconNamespaceSummaryManager.clearNSSummaryTable(); @@ -122,6 +135,8 @@ public Pair reprocess(OMMetadataManager omMetadataManager) { .reprocessWithFSO(omMetadataManager)); tasks.add(() -> nsSummaryTaskWithLegacy .reprocessWithLegacy(reconOMMetadataManager)); + tasks.add(() -> nsSummaryTaskWithOBS + .reprocessWithOBS(reconOMMetadataManager)); List> results; ThreadFactory threadFactory = new ThreadFactoryBuilder() @@ -137,17 +152,24 @@ public Pair reprocess(OMMetadataManager omMetadataManager) { } } } catch (InterruptedException ex) { - LOG.error("Error while reprocessing NSSummary " + - "table in Recon DB. ", ex); + LOG.error("Error while reprocessing NSSummary table in Recon DB.", ex); return new ImmutablePair<>(getTaskName(), false); } catch (ExecutionException ex2) { - LOG.error("Error while reprocessing NSSummary " + - "table in Recon DB. ", ex2); + LOG.error("Error while reprocessing NSSummary table in Recon DB.", ex2); return new ImmutablePair<>(getTaskName(), false); } finally { executorService.shutdown(); + + long endTime = System.nanoTime(); + // Convert to milliseconds + long durationInMillis = + TimeUnit.NANOSECONDS.toMillis(endTime - startTime); + + // Log performance metrics + LOG.info("Task execution time: {} milliseconds", durationInMillis); } + return new ImmutablePair<>(getTaskName(), true); } -} +} diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/tasks/NSSummaryTaskDbEventHandler.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/tasks/NSSummaryTaskDbEventHandler.java index f00d83e64a52..888ec5319f2f 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/tasks/NSSummaryTaskDbEventHandler.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/tasks/NSSummaryTaskDbEventHandler.java @@ -132,6 +132,8 @@ protected void handlePutDirEvent(OmDirectoryInfo directoryInfo, curNSSummary = new NSSummary(); } curNSSummary.setDirName(dirName); + // Set the parent directory ID + curNSSummary.setParentId(parentObjectId); nsSummaryMap.put(objectId, curNSSummary); // Write the child dir list to the parent directory diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/tasks/NSSummaryTaskWithLegacy.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/tasks/NSSummaryTaskWithLegacy.java index ec1ccd0542fc..4555b976ffed 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/tasks/NSSummaryTaskWithLegacy.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/tasks/NSSummaryTaskWithLegacy.java @@ -47,7 +47,7 @@ */ public class NSSummaryTaskWithLegacy extends NSSummaryTaskDbEventHandler { - private static final BucketLayout BUCKET_LAYOUT = BucketLayout.LEGACY; + private static final BucketLayout LEGACY_BUCKET_LAYOUT = BucketLayout.LEGACY; private static final Logger LOG = LoggerFactory.getLogger(NSSummaryTaskWithLegacy.class); @@ -71,16 +71,17 @@ public NSSummaryTaskWithLegacy(ReconNamespaceSummaryManager public boolean processWithLegacy(OMUpdateEventBatch events) { Iterator eventIterator = events.getIterator(); Map nsSummaryMap = new HashMap<>(); + ReconOMMetadataManager metadataManager = getReconOMMetadataManager(); while (eventIterator.hasNext()) { - OMDBUpdateEvent omdbUpdateEvent = eventIterator.next(); + OMDBUpdateEvent omdbUpdateEvent = + eventIterator.next(); OMDBUpdateEvent.OMDBUpdateAction action = omdbUpdateEvent.getAction(); // we only process updates on OM's KeyTable String table = omdbUpdateEvent.getTable(); - boolean updateOnKeyTable = table.equals(KEY_TABLE); - if (!updateOnKeyTable) { + + if (!table.equals(KEY_TABLE)) { continue; } @@ -90,102 +91,26 @@ public boolean processWithLegacy(OMUpdateEventBatch events) { OMDBUpdateEvent keyTableUpdateEvent = omdbUpdateEvent; Object value = keyTableUpdateEvent.getValue(); Object oldValue = keyTableUpdateEvent.getOldValue(); + if (!(value instanceof OmKeyInfo)) { LOG.warn("Unexpected value type {} for key {}. Skipping processing.", value.getClass().getName(), updatedKey); continue; } + OmKeyInfo updatedKeyInfo = (OmKeyInfo) value; OmKeyInfo oldKeyInfo = (OmKeyInfo) oldValue; - // KeyTable entries belong to both Legacy and OBS buckets. - // Check bucket layout and if it's OBS - // continue to the next iteration. - // Check just for the current KeyInfo. - String volumeName = updatedKeyInfo.getVolumeName(); - String bucketName = updatedKeyInfo.getBucketName(); - String bucketDBKey = getReconOMMetadataManager() - .getBucketKey(volumeName, bucketName); - // Get bucket info from bucket table - OmBucketInfo omBucketInfo = getReconOMMetadataManager() - .getBucketTable().getSkipCache(bucketDBKey); - - if (omBucketInfo.getBucketLayout() - .isObjectStore(enableFileSystemPaths)) { + if (!isBucketLayoutValid(metadataManager, updatedKeyInfo)) { continue; } - setKeyParentID(updatedKeyInfo); - - if (!updatedKeyInfo.getKeyName().endsWith(OM_KEY_PREFIX)) { - switch (action) { - case PUT: - handlePutKeyEvent(updatedKeyInfo, nsSummaryMap); - break; - - case DELETE: - handleDeleteKeyEvent(updatedKeyInfo, nsSummaryMap); - break; - - case UPDATE: - if (oldKeyInfo != null) { - // delete first, then put - setKeyParentID(oldKeyInfo); - handleDeleteKeyEvent(oldKeyInfo, nsSummaryMap); - } else { - LOG.warn("Update event does not have the old keyInfo for {}.", - updatedKey); - } - handlePutKeyEvent(updatedKeyInfo, nsSummaryMap); - break; - - default: - LOG.debug("Skipping DB update event : {}", - omdbUpdateEvent.getAction()); - } + if (enableFileSystemPaths) { + processWithFileSystemLayout(updatedKeyInfo, oldKeyInfo, action, + nsSummaryMap); } else { - OmDirectoryInfo updatedDirectoryInfo = - new OmDirectoryInfo.Builder() - .setName(updatedKeyInfo.getKeyName()) - .setObjectID(updatedKeyInfo.getObjectID()) - .setParentObjectID(updatedKeyInfo.getParentObjectID()) - .build(); - - OmDirectoryInfo oldDirectoryInfo = null; - - if (oldKeyInfo != null) { - oldDirectoryInfo = - new OmDirectoryInfo.Builder() - .setName(oldKeyInfo.getKeyName()) - .setObjectID(oldKeyInfo.getObjectID()) - .setParentObjectID(oldKeyInfo.getParentObjectID()) - .build(); - } - - switch (action) { - case PUT: - handlePutDirEvent(updatedDirectoryInfo, nsSummaryMap); - break; - - case DELETE: - handleDeleteDirEvent(updatedDirectoryInfo, nsSummaryMap); - break; - - case UPDATE: - if (oldDirectoryInfo != null) { - // delete first, then put - handleDeleteDirEvent(oldDirectoryInfo, nsSummaryMap); - } else { - LOG.warn("Update event does not have the old dirInfo for {}.", - updatedKey); - } - handlePutDirEvent(updatedDirectoryInfo, nsSummaryMap); - break; - - default: - LOG.debug("Skipping DB update event : {}", - omdbUpdateEvent.getAction()); - } + processWithObjectStoreLayout(updatedKeyInfo, oldKeyInfo, action, + nsSummaryMap); } } catch (IOException ioEx) { LOG.error("Unable to process Namespace Summary data in Recon DB. ", @@ -206,12 +131,118 @@ public boolean processWithLegacy(OMUpdateEventBatch events) { return true; } + private void processWithFileSystemLayout(OmKeyInfo updatedKeyInfo, + OmKeyInfo oldKeyInfo, + OMDBUpdateEvent.OMDBUpdateAction action, + Map nsSummaryMap) + throws IOException { + setKeyParentID(updatedKeyInfo); + + if (!updatedKeyInfo.getKeyName().endsWith(OM_KEY_PREFIX)) { + switch (action) { + case PUT: + handlePutKeyEvent(updatedKeyInfo, nsSummaryMap); + break; + + case DELETE: + handleDeleteKeyEvent(updatedKeyInfo, nsSummaryMap); + break; + + case UPDATE: + if (oldKeyInfo != null) { + setKeyParentID(oldKeyInfo); + handleDeleteKeyEvent(oldKeyInfo, nsSummaryMap); + } else { + LOG.warn("Update event does not have the old keyInfo for {}.", + updatedKeyInfo.getKeyName()); + } + handlePutKeyEvent(updatedKeyInfo, nsSummaryMap); + break; + + default: + LOG.debug("Skipping DB update event for Key: {}", action); + } + } else { + OmDirectoryInfo updatedDirectoryInfo = new OmDirectoryInfo.Builder() + .setName(updatedKeyInfo.getKeyName()) + .setObjectID(updatedKeyInfo.getObjectID()) + .setParentObjectID(updatedKeyInfo.getParentObjectID()) + .build(); + + OmDirectoryInfo oldDirectoryInfo = null; + + if (oldKeyInfo != null) { + oldDirectoryInfo = + new OmDirectoryInfo.Builder() + .setName(oldKeyInfo.getKeyName()) + .setObjectID(oldKeyInfo.getObjectID()) + .setParentObjectID(oldKeyInfo.getParentObjectID()) + .build(); + } + + switch (action) { + case PUT: + handlePutDirEvent(updatedDirectoryInfo, nsSummaryMap); + break; + + case DELETE: + handleDeleteDirEvent(updatedDirectoryInfo, nsSummaryMap); + break; + + case UPDATE: + if (oldDirectoryInfo != null) { + handleDeleteDirEvent(oldDirectoryInfo, nsSummaryMap); + } else { + LOG.warn("Update event does not have the old dirInfo for {}.", + updatedKeyInfo.getKeyName()); + } + handlePutDirEvent(updatedDirectoryInfo, nsSummaryMap); + break; + + default: + LOG.debug("Skipping DB update event for Directory: {}", action); + } + } + } + + private void processWithObjectStoreLayout(OmKeyInfo updatedKeyInfo, + OmKeyInfo oldKeyInfo, + OMDBUpdateEvent.OMDBUpdateAction action, + Map nsSummaryMap) + throws IOException { + setParentBucketId(updatedKeyInfo); + + switch (action) { + case PUT: + handlePutKeyEvent(updatedKeyInfo, nsSummaryMap); + break; + + case DELETE: + handleDeleteKeyEvent(updatedKeyInfo, nsSummaryMap); + break; + + case UPDATE: + if (oldKeyInfo != null) { + setParentBucketId(oldKeyInfo); + handleDeleteKeyEvent(oldKeyInfo, nsSummaryMap); + } else { + LOG.warn("Update event does not have the old keyInfo for {}.", + updatedKeyInfo.getKeyName()); + } + handlePutKeyEvent(updatedKeyInfo, nsSummaryMap); + break; + + default: + LOG.debug("Skipping DB update event for Key: {}", action); + } + } + public boolean reprocessWithLegacy(OMMetadataManager omMetadataManager) { Map nsSummaryMap = new HashMap<>(); try { Table keyTable = - omMetadataManager.getKeyTable(BUCKET_LAYOUT); + omMetadataManager.getKeyTable(LEGACY_BUCKET_LAYOUT); try (TableIterator> keyTableIter = keyTable.iterator()) { @@ -223,30 +254,29 @@ public boolean reprocessWithLegacy(OMMetadataManager omMetadataManager) { // KeyTable entries belong to both Legacy and OBS buckets. // Check bucket layout and if it's OBS // continue to the next iteration. - String volumeName = keyInfo.getVolumeName(); - String bucketName = keyInfo.getBucketName(); - String bucketDBKey = omMetadataManager - .getBucketKey(volumeName, bucketName); - // Get bucket info from bucket table - OmBucketInfo omBucketInfo = omMetadataManager - .getBucketTable().getSkipCache(bucketDBKey); - - if (omBucketInfo.getBucketLayout() - .isObjectStore(enableFileSystemPaths)) { + if (!isBucketLayoutValid((ReconOMMetadataManager) omMetadataManager, + keyInfo)) { continue; } - setKeyParentID(keyInfo); - - if (keyInfo.getKeyName().endsWith(OM_KEY_PREFIX)) { - OmDirectoryInfo directoryInfo = - new OmDirectoryInfo.Builder() - .setName(keyInfo.getKeyName()) - .setObjectID(keyInfo.getObjectID()) - .setParentObjectID(keyInfo.getParentObjectID()) - .build(); - handlePutDirEvent(directoryInfo, nsSummaryMap); + if (enableFileSystemPaths) { + // The LEGACY bucket is a file system bucket. + setKeyParentID(keyInfo); + + if (keyInfo.getKeyName().endsWith(OM_KEY_PREFIX)) { + OmDirectoryInfo directoryInfo = + new OmDirectoryInfo.Builder() + .setName(keyInfo.getKeyName()) + .setObjectID(keyInfo.getObjectID()) + .setParentObjectID(keyInfo.getParentObjectID()) + .build(); + handlePutDirEvent(directoryInfo, nsSummaryMap); + } else { + handlePutKeyEvent(keyInfo, nsSummaryMap); + } } else { + // The LEGACY bucket is an object store bucket. + setParentBucketId(keyInfo); handlePutKeyEvent(keyInfo, nsSummaryMap); } if (!checkAndCallFlushToDB(nsSummaryMap)) { @@ -290,7 +320,7 @@ private void setKeyParentID(OmKeyInfo keyInfo) throws IOException { getReconOMMetadataManager().getOzoneKey(keyInfo.getVolumeName(), keyInfo.getBucketName(), parentKeyName); OmKeyInfo parentKeyInfo = getReconOMMetadataManager() - .getKeyTable(BUCKET_LAYOUT) + .getKeyTable(LEGACY_BUCKET_LAYOUT) .getSkipCache(fullParentKeyName); if (parentKeyInfo != null) { @@ -300,17 +330,53 @@ private void setKeyParentID(OmKeyInfo keyInfo) throws IOException { "NSSummaryTaskWithLegacy is null"); } } else { - String bucketKey = getReconOMMetadataManager() - .getBucketKey(keyInfo.getVolumeName(), keyInfo.getBucketName()); - OmBucketInfo parentBucketInfo = - getReconOMMetadataManager().getBucketTable().getSkipCache(bucketKey); + setParentBucketId(keyInfo); + } + } - if (parentBucketInfo != null) { - keyInfo.setParentObjectID(parentBucketInfo.getObjectID()); - } else { - throw new IOException("ParentKeyInfo for " + - "NSSummaryTaskWithLegacy is null"); - } + /** + * Set the parent object ID for a bucket. + *@paramkeyInfo + *@throwsIOException + */ + private void setParentBucketId(OmKeyInfo keyInfo) + throws IOException { + String bucketKey = getReconOMMetadataManager() + .getBucketKey(keyInfo.getVolumeName(), keyInfo.getBucketName()); + OmBucketInfo parentBucketInfo = + getReconOMMetadataManager().getBucketTable().getSkipCache(bucketKey); + + if (parentBucketInfo != null) { + keyInfo.setParentObjectID(parentBucketInfo.getObjectID()); + } else { + throw new IOException("ParentKeyInfo for " + + "NSSummaryTaskWithLegacy is null"); } } + + /** + * Check if the bucket layout is LEGACY. + * @param metadataManager + * @param keyInfo + * @return + */ + private boolean isBucketLayoutValid(ReconOMMetadataManager metadataManager, + OmKeyInfo keyInfo) + throws IOException { + String volumeName = keyInfo.getVolumeName(); + String bucketName = keyInfo.getBucketName(); + String bucketDBKey = metadataManager.getBucketKey(volumeName, bucketName); + OmBucketInfo omBucketInfo = + metadataManager.getBucketTable().getSkipCache(bucketDBKey); + + if (omBucketInfo.getBucketLayout() != LEGACY_BUCKET_LAYOUT) { + LOG.debug( + "Skipping processing for bucket {} as bucket layout is not LEGACY", + bucketName); + return false; + } + + return true; + } + } diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/tasks/NSSummaryTaskWithOBS.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/tasks/NSSummaryTaskWithOBS.java new file mode 100644 index 000000000000..34c7dc967c3a --- /dev/null +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/tasks/NSSummaryTaskWithOBS.java @@ -0,0 +1,236 @@ +/* + * 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.ozone.recon.tasks; + +import org.apache.hadoop.hdds.conf.OzoneConfiguration; +import org.apache.hadoop.hdds.utils.db.Table; +import org.apache.hadoop.hdds.utils.db.TableIterator; +import org.apache.hadoop.ozone.om.OMMetadataManager; +import org.apache.hadoop.ozone.om.helpers.BucketLayout; +import org.apache.hadoop.ozone.om.helpers.OmBucketInfo; +import org.apache.hadoop.ozone.om.helpers.OmKeyInfo; +import org.apache.hadoop.ozone.om.helpers.WithParentObjectId; +import org.apache.hadoop.ozone.recon.api.types.NSSummary; +import org.apache.hadoop.ozone.recon.recovery.ReconOMMetadataManager; +import org.apache.hadoop.ozone.recon.spi.ReconNamespaceSummaryManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import static org.apache.hadoop.ozone.om.OmMetadataManagerImpl.KEY_TABLE; + + +/** + * Class for handling OBS specific tasks. + */ +public class NSSummaryTaskWithOBS extends NSSummaryTaskDbEventHandler { + + private static final BucketLayout BUCKET_LAYOUT = BucketLayout.OBJECT_STORE; + + private static final Logger LOG = + LoggerFactory.getLogger(NSSummaryTaskWithOBS.class); + + + public NSSummaryTaskWithOBS( + ReconNamespaceSummaryManager reconNamespaceSummaryManager, + ReconOMMetadataManager reconOMMetadataManager, + OzoneConfiguration ozoneConfiguration) { + super(reconNamespaceSummaryManager, + reconOMMetadataManager, ozoneConfiguration); + } + + + public boolean reprocessWithOBS(OMMetadataManager omMetadataManager) { + Map nsSummaryMap = new HashMap<>(); + + try { + Table keyTable = + omMetadataManager.getKeyTable(BUCKET_LAYOUT); + + try (TableIterator> + keyTableIter = keyTable.iterator()) { + + while (keyTableIter.hasNext()) { + Table.KeyValue kv = keyTableIter.next(); + OmKeyInfo keyInfo = kv.getValue(); + + // KeyTable entries belong to both Legacy and OBS buckets. + // Check bucket layout and if it's anything other than OBS, + // continue to the next iteration. + String volumeName = keyInfo.getVolumeName(); + String bucketName = keyInfo.getBucketName(); + String bucketDBKey = omMetadataManager + .getBucketKey(volumeName, bucketName); + // Get bucket info from bucket table + OmBucketInfo omBucketInfo = omMetadataManager + .getBucketTable().getSkipCache(bucketDBKey); + + if (omBucketInfo.getBucketLayout() != BUCKET_LAYOUT) { + continue; + } + + setKeyParentID(keyInfo); + + handlePutKeyEvent(keyInfo, nsSummaryMap); + if (!checkAndCallFlushToDB(nsSummaryMap)) { + return false; + } + } + } + } catch (IOException ioEx) { + LOG.error("Unable to reprocess Namespace Summary data in Recon DB. ", + ioEx); + return false; + } + + // flush and commit left out entries at end + if (!flushAndCommitNSToDB(nsSummaryMap)) { + return false; + } + LOG.info("Completed a reprocess run of NSSummaryTaskWithOBS"); + return true; + } + + public boolean processWithOBS(OMUpdateEventBatch events) { + Iterator eventIterator = events.getIterator(); + Map nsSummaryMap = new HashMap<>(); + + while (eventIterator.hasNext()) { + OMDBUpdateEvent omdbUpdateEvent = + eventIterator.next(); + OMDBUpdateEvent.OMDBUpdateAction action = omdbUpdateEvent.getAction(); + + // We only process updates on OM's KeyTable + String table = omdbUpdateEvent.getTable(); + boolean updateOnKeyTable = table.equals(KEY_TABLE); + if (!updateOnKeyTable) { + continue; + } + + String updatedKey = omdbUpdateEvent.getKey(); + + try { + OMDBUpdateEvent keyTableUpdateEvent = omdbUpdateEvent; + Object value = keyTableUpdateEvent.getValue(); + Object oldValue = keyTableUpdateEvent.getOldValue(); + if (value == null) { + LOG.warn("Value is null for key {}. Skipping processing.", + updatedKey); + continue; + } else if (!(value instanceof OmKeyInfo)) { + LOG.warn("Unexpected value type {} for key {}. Skipping processing.", + value.getClass().getName(), updatedKey); + continue; + } + + OmKeyInfo updatedKeyInfo = (OmKeyInfo) value; + OmKeyInfo oldKeyInfo = (OmKeyInfo) oldValue; + + // KeyTable entries belong to both OBS and Legacy buckets. + // Check bucket layout and if it's anything other than OBS, + // continue to the next iteration. + String volumeName = updatedKeyInfo.getVolumeName(); + String bucketName = updatedKeyInfo.getBucketName(); + String bucketDBKey = + getReconOMMetadataManager().getBucketKey(volumeName, bucketName); + // Get bucket info from bucket table + OmBucketInfo omBucketInfo = getReconOMMetadataManager().getBucketTable() + .getSkipCache(bucketDBKey); + + if (omBucketInfo.getBucketLayout() != BUCKET_LAYOUT) { + continue; + } + + setKeyParentID(updatedKeyInfo); + + switch (action) { + case PUT: + handlePutKeyEvent(updatedKeyInfo, nsSummaryMap); + break; + case DELETE: + handleDeleteKeyEvent(updatedKeyInfo, nsSummaryMap); + break; + case UPDATE: + if (oldKeyInfo != null) { + // delete first, then put + setKeyParentID(oldKeyInfo); + handleDeleteKeyEvent(oldKeyInfo, nsSummaryMap); + } else { + LOG.warn("Update event does not have the old keyInfo for {}.", + updatedKey); + } + handlePutKeyEvent(updatedKeyInfo, nsSummaryMap); + break; + default: + LOG.debug("Skipping DB update event: {}", action); + } + + if (!checkAndCallFlushToDB(nsSummaryMap)) { + return false; + } + } catch (IOException ioEx) { + LOG.error("Unable to process Namespace Summary data in Recon DB. ", + ioEx); + return false; + } + if (!checkAndCallFlushToDB(nsSummaryMap)) { + return false; + } + } + + // Flush and commit left-out entries at the end + if (!flushAndCommitNSToDB(nsSummaryMap)) { + return false; + } + + LOG.info("Completed a process run of NSSummaryTaskWithOBS"); + return true; + } + + + /** + * KeyTable entries don't have the parentId set. + * In order to reuse the existing methods that rely on + * the parentId, we have to set it explicitly. + * Note: For an OBS key, the parentId will always correspond to the ID of the + * OBS bucket in which it is located. + * + * @param keyInfo + * @throws IOException + */ + private void setKeyParentID(OmKeyInfo keyInfo) + throws IOException { + String bucketKey = getReconOMMetadataManager() + .getBucketKey(keyInfo.getVolumeName(), keyInfo.getBucketName()); + OmBucketInfo parentBucketInfo = + getReconOMMetadataManager().getBucketTable().getSkipCache(bucketKey); + + if (parentBucketInfo != null) { + keyInfo.setParentObjectID(parentBucketInfo.getObjectID()); + } else { + throw new IOException("ParentKeyInfo for " + + "NSSummaryTaskWithOBS is null"); + } + } + +} diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/api/db.json b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/api/db.json index 60362299fa55..204609f66fec 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/api/db.json +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/api/db.json @@ -29,7 +29,8 @@ "storageReport": { "capacity": 62725623808, "used": 488288256, - "remaining": 21005319168 + "remaining": 21005319168, + "committed": 10240000 }, "pipelines": [ { @@ -62,7 +63,8 @@ "storageReport": { "capacity": 549755813888, "used": 450971566080, - "remaining": 95784247808 + "remaining": 95784247808, + "committed": 34563456 }, "pipelines": [ { @@ -95,7 +97,8 @@ "storageReport": { "capacity": 549755813888, "used": 450971566080, - "remaining": 95784247808 + "remaining": 95784247808, + "committed": 34562 }, "pipelines": [ { @@ -128,7 +131,8 @@ "storageReport": { "capacity": 549755813888, "used": 450971566080, - "remaining": 95784247808 + "remaining": 95784247808, + "committed": 4576435 }, "pipelines": [ { @@ -161,7 +165,8 @@ "storageReport": { "capacity": 549755813888, "used": 450971566080, - "remaining": 95784247808 + "remaining": 95784247808, + "committed": 3453121 }, "pipelines": [ { @@ -194,7 +199,8 @@ "storageReport": { "capacity": 140737488355328, "used": 43980465111040, - "remaining": 86757023244288 + "remaining": 86757023244288, + "committed": 3457623435 }, "pipelines": [ { @@ -233,7 +239,8 @@ "storageReport": { "capacity": 140737488355328, "used": 43980465111040, - "remaining": 86757023244288 + "remaining": 86757023244288, + "committed": 345624 }, "pipelines": [ { @@ -272,7 +279,8 @@ "storageReport": { "capacity": 140737488355328, "used": 43980465111040, - "remaining": 86757023244288 + "remaining": 86757023244288, + "committed": 123464574 }, "pipelines": [ { @@ -311,7 +319,8 @@ "storageReport": { "capacity": 140737488355328, "used": 43980465111040, - "remaining": 86757023244288 + "remaining": 86757023244288, + "committed": 556721345 }, "pipelines": [ { @@ -350,7 +359,8 @@ "storageReport": { "capacity": 140737488355328, "used": 43980465111040, - "remaining": 86757023244288 + "remaining": 86757023244288, + "committed": 45671235234 }, "pipelines": [ { @@ -389,7 +399,8 @@ "storageReport": { "capacity": 140737488355328, "used": 0, - "remaining": 110737488355328 + "remaining": 110737488355328, + "committed": 0 }, "pipelines": [], "containers": 0, @@ -409,7 +420,8 @@ "storageReport": { "capacity": 805306368000, "used": 644245094400, - "remaining": 121061273600 + "remaining": 121061273600, + "committed": 4572345234 }, "pipelines": [ { @@ -442,7 +454,8 @@ "storageReport": { "capacity": 140737488355328, "used": 43980465111040, - "remaining": 92757023244288 + "remaining": 92757023244288, + "committed": 34563453 }, "pipelines": [ { @@ -475,7 +488,8 @@ "storageReport": { "capacity": 549755813888, "used": 450971566080, - "remaining": 94784247808 + "remaining": 94784247808, + "committed": 7234234 }, "pipelines": [ { @@ -514,7 +528,8 @@ "storageReport": { "capacity": 140737488355328, "used": 43980465111040, - "remaining": 92757023244288 + "remaining": 92757023244288, + "committed": 34562346 }, "pipelines": [ { @@ -547,7 +562,8 @@ "storageReport": { "capacity": 140737488355328, "used": 43980465111040, - "remaining": 76757023244288 + "remaining": 76757023244288, + "committed": 834324523 }, "pipelines": [ { @@ -580,7 +596,8 @@ "storageReport": { "capacity": 140737488355328, "used": 43980465111040, - "remaining": 66757023244288 + "remaining": 66757023244288, + "committed": 346467345 }, "pipelines": [ { @@ -619,7 +636,8 @@ "storageReport": { "capacity": 140737488355328, "used": 43980465111040, - "remaining": 96157023244288 + "remaining": 96157023244288, + "committed": 45245456 }, "pipelines": [ { @@ -652,7 +670,8 @@ "storageReport": { "capacity": 140737488355328, "used": 43980465111040, - "remaining": 94757023244288 + "remaining": 94757023244288, + "committed": 45673234 }, "pipelines": [ { diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/package.json b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/package.json index 2282a6e7ce59..f5c7d5afde44 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/package.json +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/package.json @@ -69,7 +69,7 @@ "json-server": "^0.15.1", "npm-run-all": "^4.1.5", "prettier": "^2.8.4", - "vite": "4.4.0", + "vite": "4.5.3", "vite-plugin-svgr": "^4.2.0", "vite-tsconfig-paths": "^3.6.0", "vitest": "^1.6.0" diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/pnpm-lock.yaml b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/pnpm-lock.yaml index faa1500dfb2d..790f4ed7f5e8 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/pnpm-lock.yaml +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/pnpm-lock.yaml @@ -81,7 +81,7 @@ devDependencies: version: 5.62.0(eslint@7.32.0)(typescript@4.9.5) '@vitejs/plugin-react': specifier: ^4.0.0 - version: 4.3.1(vite@4.4.0) + version: 4.3.1(vite@4.5.3) eslint: specifier: ^7.28.0 version: 7.32.0 @@ -101,14 +101,14 @@ devDependencies: specifier: ^2.8.4 version: 2.8.8 vite: - specifier: 4.4.0 - version: 4.4.0(less@3.13.1) + specifier: 4.5.3 + version: 4.5.3(less@3.13.1) vite-plugin-svgr: specifier: ^4.2.0 - version: 4.2.0(typescript@4.9.5)(vite@4.4.0) + version: 4.2.0(typescript@4.9.5)(vite@4.5.3) vite-tsconfig-paths: specifier: ^3.6.0 - version: 3.6.0(vite@4.4.0) + version: 3.6.0(vite@4.5.3) vitest: specifier: ^1.6.0 version: 1.6.0(less@3.13.1) @@ -1537,7 +1537,7 @@ packages: eslint-visitor-keys: 3.4.3 dev: true - /@vitejs/plugin-react@4.3.1(vite@4.4.0): + /@vitejs/plugin-react@4.3.1(vite@4.5.3): resolution: {integrity: sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: @@ -1548,7 +1548,7 @@ packages: '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.24.8) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 4.4.0(less@3.13.1) + vite: 4.5.3(less@3.13.1) transitivePeerDependencies: - supports-color dev: true @@ -6279,7 +6279,7 @@ packages: - terser dev: true - /vite-plugin-svgr@4.2.0(typescript@4.9.5)(vite@4.4.0): + /vite-plugin-svgr@4.2.0(typescript@4.9.5)(vite@4.5.3): resolution: {integrity: sha512-SC7+FfVtNQk7So0XMjrrtLAbEC8qjFPifyD7+fs/E6aaNdVde6umlVVh0QuwDLdOMu7vp5RiGFsB70nj5yo0XA==} peerDependencies: vite: ^2.6.0 || 3 || 4 || 5 @@ -6287,14 +6287,14 @@ packages: '@rollup/pluginutils': 5.1.0 '@svgr/core': 8.1.0(typescript@4.9.5) '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0) - vite: 4.4.0(less@3.13.1) + vite: 4.5.3(less@3.13.1) transitivePeerDependencies: - rollup - supports-color - typescript dev: true - /vite-tsconfig-paths@3.6.0(vite@4.4.0): + /vite-tsconfig-paths@3.6.0(vite@4.5.3): resolution: {integrity: sha512-UfsPYonxLqPD633X8cWcPFVuYzx/CMNHAjZTasYwX69sXpa4gNmQkR0XCjj82h7zhLGdTWagMjC1qfb9S+zv0A==} peerDependencies: vite: '>2.0.0-0' @@ -6303,13 +6303,13 @@ packages: globrex: 0.1.2 recrawl-sync: 2.2.3 tsconfig-paths: 4.2.0 - vite: 4.4.0(less@3.13.1) + vite: 4.5.3(less@3.13.1) transitivePeerDependencies: - supports-color dev: true - /vite@4.4.0(less@3.13.1): - resolution: {integrity: sha512-Wf+DCEjuM8aGavEYiF77hnbxEZ+0+/jC9nABR46sh5Xi+GYeSvkeEFRiVuI3x+tPjxgZeS91h1jTAQTPFgePpA==} + /vite@4.5.3(less@3.13.1): + resolution: {integrity: sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true peerDependencies: diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/components/storageBar/storageBar.less b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/components/storageBar/storageBar.less index f8e538e574e8..9e3186094ce7 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/components/storageBar/storageBar.less +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/components/storageBar/storageBar.less @@ -19,6 +19,7 @@ @progress-gray: #d0d0d0; @progress-blue: #1890ff; @progress-green: #52c41a; +@progress-dark-grey: #424242; .storage-cell-container { position: relative; @@ -44,3 +45,7 @@ .remaining-bg { color: @progress-gray; } + +.committed-bg { + color: @progress-dark-grey; +} diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/components/storageBar/storageBar.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/components/storageBar/storageBar.tsx index 3c3140463325..cbb2d5642f45 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/components/storageBar/storageBar.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/components/storageBar/storageBar.tsx @@ -34,6 +34,7 @@ interface IStorageBarProps extends RouteComponentProps { total: number; used: number; remaining: number; + committed: number; showMeta?: boolean; } @@ -41,6 +42,7 @@ const defaultProps = { total: 0, used: 0, remaining: 0, + committed: 0, showMeta: true }; @@ -48,7 +50,7 @@ class StorageBar extends React.Component { static defaultProps = defaultProps; render() { - const { total, used, remaining, showMeta } = this.props; + const { total, used, remaining, committed, showMeta } = this.props; const nonOzoneUsed = total - remaining - used; const totalUsed = total - remaining; const tooltip = ( @@ -56,6 +58,7 @@ class StorageBar extends React.Component {
Ozone Used ({size(used)})
Non Ozone Used ({size(nonOzoneUsed)})
Remaining ({size(remaining)})
+
Container Pre-allocated ({size(committed)})
); const metaElement = showMeta ?
{size(used)} + {size(nonOzoneUsed)} / {size(total)}
: null; diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/types/datanode.types.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/types/datanode.types.tsx index 8f92742916f3..d69466ac0fea 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/types/datanode.types.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/types/datanode.types.tsx @@ -30,4 +30,5 @@ export interface IStorageReport { capacity: number; used: number; remaining: number; + committed: number; } diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/datanodes/datanodes.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/datanodes/datanodes.tsx index 9e15c90d898b..0cffadd036f2 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/datanodes/datanodes.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/datanodes/datanodes.tsx @@ -73,6 +73,7 @@ interface IDatanode { storageUsed: number; storageTotal: number; storageRemaining: number; + storageCommitted: number; pipelines: IPipeline[]; containers: number; openContainers: number; @@ -177,7 +178,7 @@ const COLUMNS = [ render: (text: string, record: IDatanode) => ( + remaining={record.storageRemaining} committed={record.storageCommitted}/> ) }, { @@ -363,6 +364,7 @@ export class Datanodes extends React.Component, IDatanode storageUsed: datanode.storageReport.used, storageTotal: datanode.storageReport.capacity, storageRemaining: datanode.storageReport.remaining, + storageCommitted: datanode.storageReport.committed, pipelines: datanode.pipelines, containers: datanode.containers, openContainers: datanode.openContainers, diff --git a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/OMMetadataManagerTestUtils.java b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/OMMetadataManagerTestUtils.java index 42d69e030f31..8240c6c8c856 100644 --- a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/OMMetadataManagerTestUtils.java +++ b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/OMMetadataManagerTestUtils.java @@ -65,6 +65,7 @@ */ public final class OMMetadataManagerTestUtils { + private static OzoneConfiguration configuration; private OMMetadataManagerTestUtils() { } @@ -129,8 +130,9 @@ public static ReconOMMetadataManager getTestReconOmMetadataManager( DBCheckpoint checkpoint = omMetadataManager.getStore() .getCheckpoint(true); assertNotNull(checkpoint.getCheckpointLocation()); - - OzoneConfiguration configuration = new OzoneConfiguration(); + if (configuration == null) { + configuration = new OzoneConfiguration(); + } configuration.set(OZONE_RECON_OM_SNAPSHOT_DB_DIR, reconOmDbDir .getAbsolutePath()); @@ -493,4 +495,14 @@ public static OmKeyLocationInfo getOmKeyLocationInfo(BlockID blockID, public static BucketLayout getBucketLayout() { return BucketLayout.DEFAULT; } + + public static OzoneConfiguration getConfiguration() { + return configuration; + } + + public static void setConfiguration( + OzoneConfiguration configuration) { + OMMetadataManagerTestUtils.configuration = configuration; + } + } diff --git a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestNSSummaryDiskUsageOrdering.java b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestNSSummaryDiskUsageOrdering.java new file mode 100644 index 000000000000..a244e4ff2ce2 --- /dev/null +++ b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestNSSummaryDiskUsageOrdering.java @@ -0,0 +1,421 @@ +/* + * 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.ozone.recon.api; + +import org.apache.hadoop.hdds.conf.OzoneConfiguration; +import org.apache.hadoop.ozone.om.OMMetadataManager; +import org.apache.hadoop.ozone.om.helpers.BucketLayout; +import org.apache.hadoop.ozone.om.helpers.OmBucketInfo; +import org.apache.hadoop.ozone.om.helpers.OmVolumeArgs; +import org.apache.hadoop.ozone.recon.api.types.DUResponse; + +import org.apache.hadoop.ozone.recon.spi.ReconNamespaceSummaryManager; +import org.apache.hadoop.ozone.recon.spi.StorageContainerServiceProvider; +import org.apache.hadoop.ozone.recon.spi.impl.OzoneManagerServiceProviderImpl; +import org.apache.hadoop.ozone.recon.spi.impl.StorageContainerServiceProviderImpl; +import org.apache.hadoop.ozone.recon.tasks.NSSummaryTaskWithFSO; +import org.junit.jupiter.api.BeforeEach; +import static org.apache.hadoop.ozone.recon.OMMetadataManagerTestUtils.getMockOzoneManagerServiceProviderWithFSO; +import static org.apache.hadoop.ozone.recon.OMMetadataManagerTestUtils.getTestReconOmMetadataManager; +import static org.apache.hadoop.ozone.recon.ReconServerConfigKeys.OZONE_RECON_NSSUMMARY_FLUSH_TO_DB_MAX_THRESHOLD; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import org.apache.hadoop.hdds.scm.container.ContainerManager; +import org.apache.hadoop.hdds.scm.container.ContainerNotFoundException; +import org.apache.hadoop.hdds.scm.server.OzoneStorageContainerManager; +import org.apache.hadoop.ozone.om.OmMetadataManagerImpl; +import org.apache.hadoop.ozone.recon.ReconTestInjector; +import org.apache.hadoop.ozone.recon.recovery.ReconOMMetadataManager; +import org.apache.hadoop.ozone.recon.scm.ReconNodeManager; +import org.apache.hadoop.ozone.recon.scm.ReconStorageContainerManagerFacade; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import javax.ws.rs.core.Response; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.UUID; + +import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_DB_DIRS; +import static org.apache.hadoop.ozone.recon.OMMetadataManagerTestUtils.writeDirToOm; +import static org.apache.hadoop.ozone.recon.OMMetadataManagerTestUtils.writeKeyToOm; +import static org.mockito.Mockito.when; + +/** + * Test NSSummary Disk Usage subpath ordering. + */ +public class TestNSSummaryDiskUsageOrdering { + + @TempDir + private Path temporaryFolder; + + private ReconOMMetadataManager reconOMMetadataManager; + private NSSummaryEndpoint nsSummaryEndpoint; + private OzoneConfiguration ozoneConfiguration; + private static final String ROOT_PATH = "/"; + private static final String TEST_USER = "TestUser"; + private OMMetadataManager omMetadataManager; + @BeforeEach + public void setUp() throws Exception { + ozoneConfiguration = new OzoneConfiguration(); + ozoneConfiguration.setLong(OZONE_RECON_NSSUMMARY_FLUSH_TO_DB_MAX_THRESHOLD, + 100); + omMetadataManager = initializeNewOmMetadataManager( + Files.createDirectory(temporaryFolder.resolve("JunitOmDBDir")) + .toFile()); + OzoneManagerServiceProviderImpl ozoneManagerServiceProvider = + getMockOzoneManagerServiceProviderWithFSO(); + reconOMMetadataManager = getTestReconOmMetadataManager(omMetadataManager, + Files.createDirectory(temporaryFolder.resolve("OmMetataDir")).toFile()); + + ReconTestInjector reconTestInjector = + new ReconTestInjector.Builder(temporaryFolder.toFile()) + .withReconOm(reconOMMetadataManager) + .withOmServiceProvider(ozoneManagerServiceProvider) + .withReconSqlDb() + .withContainerDB() + .addBinding(OzoneStorageContainerManager.class, + getMockReconSCM()) + .addBinding(StorageContainerServiceProvider.class, + mock(StorageContainerServiceProviderImpl.class)) + .addBinding(NSSummaryEndpoint.class) + .build(); + ReconNamespaceSummaryManager reconNamespaceSummaryManager = + reconTestInjector.getInstance(ReconNamespaceSummaryManager.class); + nsSummaryEndpoint = reconTestInjector.getInstance(NSSummaryEndpoint.class); + + // populate OM DB and reprocess into Recon RocksDB + populateOMDB(); + NSSummaryTaskWithFSO nSSummaryTaskWithFso = + new NSSummaryTaskWithFSO(reconNamespaceSummaryManager, + reconOMMetadataManager, ozoneConfiguration); + nSSummaryTaskWithFso.reprocessWithFSO(reconOMMetadataManager); + } + + /** + * Create a new OM Metadata manager instance with one user, one vol, and two + * buckets. + * @throws IOException ioEx + */ + private static OMMetadataManager initializeNewOmMetadataManager( + File omDbDir) + throws IOException { + OzoneConfiguration omConfiguration = new OzoneConfiguration(); + omConfiguration.set(OZONE_OM_DB_DIRS, + omDbDir.getAbsolutePath()); + OMMetadataManager omMetadataManager = new OmMetadataManagerImpl( + omConfiguration, null); + return omMetadataManager; + } + + @Test + public void testDiskUsageOrderingForRoot() throws Exception { + // root level DU + // Verify the ordering of subpaths under the root + verifyOrdering(ROOT_PATH); + } + + @Test + public void testDiskUsageOrderingForVolume() throws Exception { + // volume level DU + // Verify the ordering of subpaths under the volume + verifyOrdering("/volA"); + verifyOrdering("/volB"); + } + + @Test + public void testDiskUsageOrderingForBucket() throws Exception { + // bucket level DU + // Verify the ordering of subpaths under the bucket + verifyOrdering("/volA/bucketA1"); + verifyOrdering("/volA/bucketA2"); + verifyOrdering("/volA/bucketA3"); + verifyOrdering("/volB/bucketB1"); + } + + private void verifyOrdering(String path) + throws IOException { + Response response = + nsSummaryEndpoint.getDiskUsage(path, true, false, true); + DUResponse duRes = (DUResponse) response.getEntity(); + List duData = duRes.getDuData(); + List sortedDuData = new ArrayList<>(duData); + // Sort the DU data by size in descending order to compare with the original. + sortedDuData.sort( + Comparator.comparingLong(DUResponse.DiskUsage::getSize).reversed()); + + for (int i = 0; i < duData.size(); i++) { + assertEquals(sortedDuData.get(i).getSubpath(), + duData.get(i).getSubpath(), + "DU-Sub-Path under " + path + + " should be sorted by descending order of size"); + } + } + + /** + * Tests the NSSummaryEndpoint for a given volume, bucket, and directory structure. + * The test setup mimics the following filesystem structure with specified sizes: + * + * root + * ├── volA + * │ ├── bucketA1 + * │ │ ├── fileA1 (Size: 600KB) + * │ │ ├── fileA2 (Size: 80KB) + * │ │ ├── dirA1 (Total Size: 1500KB) + * │ │ ├── dirA2 (Total Size: 1700KB) + * │ │ └── dirA3 (Total Size: 1300KB) + * │ ├── bucketA2 + * │ │ ├── fileA3 (Size: 200KB) + * │ │ ├── fileA4 (Size: 4000KB) + * │ │ ├── dirA4 (Total Size: 1100KB) + * │ │ ├── dirA5 (Total Size: 1900KB) + * │ │ └── dirA6 (Total Size: 210KB) + * │ └── bucketA3 + * │ ├── fileA5 (Size: 5000KB) + * │ ├── fileA6 (Size: 700KB) + * │ ├── dirA7 (Total Size: 1200KB) + * │ ├── dirA8 (Total Size: 1600KB) + * │ └── dirA9 (Total Size: 180KB) + * └── volB + * └── bucketB1 + * ├── fileB1 (Size: 300KB) + * ├── fileB2 (Size: 500KB) + * ├── dirB1 (Total Size: 14000KB) + * ├── dirB2 (Total Size: 1800KB) + * └── dirB3 (Total Size: 2200KB) + * + * @throws Exception + */ + private void populateOMDB() throws Exception { + // Create Volumes + long volAObjectId = createVolume("volA"); + long volBObjectId = createVolume("volB"); + + // Create Buckets in volA + long bucketA1ObjectId = + createBucket("volA", "bucketA1", 600 + 80 + 1500 + 1700 + 1300); + long bucketA2ObjectId = + createBucket("volA", "bucketA2", 200 + 4000 + 1100 + 1900 + 210); + long bucketA3ObjectId = + createBucket("volA", "bucketA3", 5000 + 700 + 1200 + 1600 + 180); + + // Create Bucket in volB + long bucketB1ObjectId = + createBucket("volB", "bucketB1", 300 + 500 + 14000 + 1800 + 2200); + + // Create Directories and Files under bucketA1 + long dirA1ObjectId = + createDirectory(bucketA1ObjectId, bucketA1ObjectId, volAObjectId, + "dirA1"); + long dirA2ObjectId = + createDirectory(bucketA1ObjectId, bucketA1ObjectId, volAObjectId, + "dirA2"); + long dirA3ObjectId = + createDirectory(bucketA1ObjectId, bucketA1ObjectId, volAObjectId, + "dirA3"); + + // Files directly under bucketA1 + createFile("fileA1", "bucketA1", "volA", "fileA1", bucketA1ObjectId, + bucketA1ObjectId, volAObjectId, 600 * 1024); + createFile("fileA2", "bucketA1", "volA", "fileA2", bucketA1ObjectId, + bucketA1ObjectId, volAObjectId, 80 * 1024); + + // Create Directories and Files under bucketA2 + long dirA4ObjectId = + createDirectory(bucketA2ObjectId, bucketA2ObjectId, volAObjectId, + "dirA4"); + long dirA5ObjectId = + createDirectory(bucketA2ObjectId, bucketA2ObjectId, volAObjectId, + "dirA5"); + long dirA6ObjectId = + createDirectory(bucketA2ObjectId, bucketA2ObjectId, volAObjectId, + "dirA6"); + + // Files directly under bucketA2 + createFile("fileA3", "bucketA2", "volA", "fileA3", bucketA2ObjectId, + bucketA2ObjectId, volAObjectId, 200 * 1024); + createFile("fileA4", "bucketA2", "volA", "fileA4", bucketA2ObjectId, + bucketA2ObjectId, volAObjectId, 4000 * 1024); + + // Create Directories and Files under bucketA3 + long dirA7ObjectId = + createDirectory(bucketA3ObjectId, bucketA3ObjectId, volAObjectId, + "dirA7"); + long dirA8ObjectId = + createDirectory(bucketA3ObjectId, bucketA3ObjectId, volAObjectId, + "dirA8"); + long dirA9ObjectId = + createDirectory(bucketA3ObjectId, bucketA3ObjectId, volAObjectId, + "dirA9"); + + // Files directly under bucketA3 + createFile("fileA5", "bucketA3", "volA", "fileA5", bucketA3ObjectId, + bucketA3ObjectId, volAObjectId, 5000 * 1024); + createFile("fileA6", "bucketA3", "volA", "fileA6", bucketA3ObjectId, + bucketA3ObjectId, volAObjectId, 700 * 1024); + + // Create Directories and Files under bucketB1 + long dirB1ObjectId = + createDirectory(bucketB1ObjectId, bucketB1ObjectId, volBObjectId, + "dirB1"); + long dirB2ObjectId = + createDirectory(bucketB1ObjectId, bucketB1ObjectId, volBObjectId, + "dirB2"); + long dirB3ObjectId = + createDirectory(bucketB1ObjectId, bucketB1ObjectId, volBObjectId, + "dirB3"); + + // Files directly under bucketB1 + createFile("fileB1", "bucketB1", "volB", "fileB1", bucketB1ObjectId, + bucketB1ObjectId, volBObjectId, 300 * 1024); + createFile("fileB2", "bucketB1", "volB", "fileB2", bucketB1ObjectId, + bucketB1ObjectId, volBObjectId, 500 * 1024); + + // Create Inner files under directories + createFile("dirA1/innerFile", "bucketA1", "volA", "innerFile", + dirA1ObjectId, bucketA1ObjectId, volAObjectId, 1500 * 1024); + createFile("dirA2/innerFile", "bucketA1", "volA", "innerFile", + dirA2ObjectId, bucketA1ObjectId, volAObjectId, 1700 * 1024); + createFile("dirA3/innerFile", "bucketA1", "volA", "innerFile", + dirA3ObjectId, bucketA1ObjectId, volAObjectId, 1300 * 1024); + createFile("dirA4/innerFile", "bucketA2", "volA", "innerFile", + dirA4ObjectId, bucketA2ObjectId, volAObjectId, 1100 * 1024); + createFile("dirA5/innerFile", "bucketA2", "volA", "innerFile", + dirA5ObjectId, bucketA2ObjectId, volAObjectId, 1900 * 1024); + createFile("dirA6/innerFile", "bucketA2", "volA", "innerFile", + dirA6ObjectId, bucketA2ObjectId, volAObjectId, 210 * 1024); + createFile("dirA7/innerFile", "bucketA3", "volA", "innerFile", + dirA7ObjectId, bucketA3ObjectId, volAObjectId, 1200 * 1024); + createFile("dirA8/innerFile", "bucketA3", "volA", "innerFile", + dirA8ObjectId, bucketA3ObjectId, volAObjectId, 1600 * 1024); + createFile("dirA9/innerFile", "bucketA3", "volA", "innerFile", + dirA9ObjectId, bucketA3ObjectId, volAObjectId, 180 * 1024); + createFile("dirB1/innerFile", "bucketB1", "volB", "innerFile", + dirB1ObjectId, bucketB1ObjectId, volBObjectId, 14000 * 1024); + createFile("dirB2/innerFile", "bucketB1", "volB", "innerFile", + dirB2ObjectId, bucketB1ObjectId, volBObjectId, 1800 * 1024); + createFile("dirB3/innerFile", "bucketB1", "volB", "innerFile", + dirB3ObjectId, bucketB1ObjectId, volBObjectId, 2200 * 1024); + } + + /** + * Create a volume and add it to the Volume Table. + * @return volume Object ID + * @throws IOException + */ + private long createVolume(String volumeName) throws Exception { + String volumeKey = reconOMMetadataManager.getVolumeKey(volumeName); + long volumeId = UUID.randomUUID().getMostSignificantBits() & + Long.MAX_VALUE; // Generate positive ID + OmVolumeArgs args = OmVolumeArgs.newBuilder() + .setObjectID(volumeId) + .setVolume(volumeName) + .setAdminName(TEST_USER) + .setOwnerName(TEST_USER) + .build(); + + reconOMMetadataManager.getVolumeTable().put(volumeKey, args); + return volumeId; + } + + /** + * Create a bucket and add it to the Bucket Table. + * @return bucket Object ID + * @throws IOException + */ + private long createBucket(String volumeName, String bucketName, long dataSize) + throws Exception { + String bucketKey = + reconOMMetadataManager.getBucketKey(volumeName, bucketName); + long bucketId = UUID.randomUUID().getMostSignificantBits() & + Long.MAX_VALUE; // Generate positive ID + OmBucketInfo bucketInfo = OmBucketInfo.newBuilder() + .setVolumeName(volumeName) + .setBucketName(bucketName) + .setObjectID(bucketId) + .setBucketLayout(getBucketLayout()) + .setUsedBytes(dataSize) + .build(); + + reconOMMetadataManager.getBucketTable().put(bucketKey, bucketInfo); + return bucketId; + } + + /** + * Create a directory and add it to the Directory Table. + * @return directory Object ID + * @throws IOException + */ + private long createDirectory(long parentObjectId, + long bucketObjectId, + long volumeObjectId, + String dirName) throws IOException { + long objectId = UUID.randomUUID().getMostSignificantBits() & + Long.MAX_VALUE; // Ensure positive ID + writeDirToOm(reconOMMetadataManager, objectId, parentObjectId, + bucketObjectId, + volumeObjectId, dirName); + return objectId; + } + + /** + * Create a file and add it to the File Table. + * @return file Object ID + * @throws IOException + */ + @SuppressWarnings("checkstyle:ParameterNumber") + private long createFile(String key, + String bucket, + String volume, + String fileName, + long parentObjectId, + long bucketObjectId, + long volumeObjectId, + long dataSize) throws IOException { + long objectId = UUID.randomUUID().getMostSignificantBits() & + Long.MAX_VALUE; // Ensure positive ID + writeKeyToOm(reconOMMetadataManager, key, bucket, volume, fileName, + objectId, + parentObjectId, bucketObjectId, volumeObjectId, dataSize, + getBucketLayout()); + return objectId; + } + + private static ReconStorageContainerManagerFacade getMockReconSCM() + throws ContainerNotFoundException { + ReconStorageContainerManagerFacade reconSCM = + mock(ReconStorageContainerManagerFacade.class); + ContainerManager containerManager = mock(ContainerManager.class); + + when(reconSCM.getContainerManager()).thenReturn(containerManager); + ReconNodeManager mockReconNodeManager = mock(ReconNodeManager.class); + when(reconSCM.getScmNodeManager()).thenReturn(mockReconNodeManager); + return reconSCM; + } + + private static BucketLayout getBucketLayout() { + return BucketLayout.FILE_SYSTEM_OPTIMIZED; + } +} diff --git a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestNSSummaryEndpointWithFSO.java b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestNSSummaryEndpointWithFSO.java index d3bee19ba6e9..54da926601e5 100644 --- a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestNSSummaryEndpointWithFSO.java +++ b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestNSSummaryEndpointWithFSO.java @@ -35,6 +35,7 @@ import org.apache.hadoop.ozone.OzoneConsts; import org.apache.hadoop.ozone.om.OMMetadataManager; import org.apache.hadoop.ozone.om.OmMetadataManagerImpl; +import org.apache.hadoop.ozone.om.helpers.OmKeyInfo; import org.apache.hadoop.ozone.om.helpers.BucketLayout; import org.apache.hadoop.ozone.om.helpers.OmVolumeArgs; import org.apache.hadoop.ozone.om.helpers.OmBucketInfo; @@ -42,12 +43,14 @@ import org.apache.hadoop.ozone.om.helpers.OmKeyLocationInfoGroup; import org.apache.hadoop.ozone.recon.ReconConstants; import org.apache.hadoop.ozone.recon.ReconTestInjector; +import org.apache.hadoop.ozone.recon.ReconUtils; import org.apache.hadoop.ozone.recon.api.handlers.BucketHandler; import org.apache.hadoop.ozone.recon.api.handlers.EntityHandler; import org.apache.hadoop.ozone.recon.api.types.DUResponse; -import org.apache.hadoop.ozone.recon.api.types.FileSizeDistributionResponse; -import org.apache.hadoop.ozone.recon.api.types.ResponseStatus; +import org.apache.hadoop.ozone.recon.api.types.NSSummary; import org.apache.hadoop.ozone.recon.api.types.QuotaUsageResponse; +import org.apache.hadoop.ozone.recon.api.types.ResponseStatus; +import org.apache.hadoop.ozone.recon.api.types.FileSizeDistributionResponse; import org.apache.hadoop.ozone.recon.common.CommonUtils; import org.apache.hadoop.ozone.recon.recovery.ReconOMMetadataManager; import org.apache.hadoop.ozone.recon.scm.ReconNodeManager; @@ -57,9 +60,12 @@ import org.apache.hadoop.ozone.recon.spi.impl.OzoneManagerServiceProviderImpl; import org.apache.hadoop.ozone.recon.spi.impl.StorageContainerServiceProviderImpl; import org.apache.hadoop.ozone.recon.tasks.NSSummaryTaskWithFSO; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import org.mockito.ArgumentCaptor; +import org.slf4j.Logger; import javax.ws.rs.core.Response; @@ -74,8 +80,6 @@ import java.util.Set; import java.util.HashSet; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.apache.hadoop.hdds.protocol.MockDatanodeDetails.randomDatanodeDetails; import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_DB_DIRS; import static org.apache.hadoop.ozone.recon.OMMetadataManagerTestUtils.writeDirToOm; @@ -83,8 +87,12 @@ import static org.apache.hadoop.ozone.recon.OMMetadataManagerTestUtils.getTestReconOmMetadataManager; import static org.apache.hadoop.ozone.recon.OMMetadataManagerTestUtils.getMockOzoneManagerServiceProviderWithFSO; import static org.apache.hadoop.ozone.recon.ReconServerConfigKeys.OZONE_RECON_NSSUMMARY_FLUSH_TO_DB_MAX_THRESHOLD; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.mockito.Mockito.anyLong; +import static org.mockito.Mockito.verify; /** * Test for NSSummary REST APIs with FSO. @@ -114,6 +122,7 @@ public class TestNSSummaryEndpointWithFSO { private Path temporaryFolder; private ReconOMMetadataManager reconOMMetadataManager; + private ReconNamespaceSummaryManager reconNamespaceSummaryManager; private NSSummaryEndpoint nsSummaryEndpoint; private OzoneConfiguration ozoneConfiguration; private CommonUtils commonUtils; @@ -375,7 +384,7 @@ public void setUp() throws Exception { mock(StorageContainerServiceProviderImpl.class)) .addBinding(NSSummaryEndpoint.class) .build(); - ReconNamespaceSummaryManager reconNamespaceSummaryManager = + this.reconNamespaceSummaryManager = reconTestInjector.getInstance(ReconNamespaceSummaryManager.class); nsSummaryEndpoint = reconTestInjector.getInstance(NSSummaryEndpoint.class); @@ -449,7 +458,7 @@ public void testGetBasicInfoKey() throws Exception { public void testDiskUsageRoot() throws Exception { // root level DU Response rootResponse = nsSummaryEndpoint.getDiskUsage(ROOT_PATH, - false, false); + false, false, false); DUResponse duRootRes = (DUResponse) rootResponse.getEntity(); assertEquals(2, duRootRes.getCount()); List duRootData = duRootRes.getDuData(); @@ -463,11 +472,12 @@ public void testDiskUsageRoot() throws Exception { assertEquals(VOL_DATA_SIZE, duVol1.getSize()); assertEquals(VOL_TWO_DATA_SIZE, duVol2.getSize()); } + @Test public void testDiskUsageVolume() throws Exception { // volume level DU Response volResponse = nsSummaryEndpoint.getDiskUsage(VOL_PATH, - false, false); + false, false, false); DUResponse duVolRes = (DUResponse) volResponse.getEntity(); assertEquals(2, duVolRes.getCount()); List duData = duVolRes.getDuData(); @@ -482,11 +492,12 @@ public void testDiskUsageVolume() throws Exception { assertEquals(BUCKET_TWO_DATA_SIZE, duBucket2.getSize()); } + @Test public void testDiskUsageBucket() throws Exception { // bucket level DU Response bucketResponse = nsSummaryEndpoint.getDiskUsage(BUCKET_ONE_PATH, - false, false); + false, false, false); DUResponse duBucketResponse = (DUResponse) bucketResponse.getEntity(); assertEquals(1, duBucketResponse.getCount()); DUResponse.DiskUsage duDir1 = duBucketResponse.getDuData().get(0); @@ -494,11 +505,12 @@ public void testDiskUsageBucket() throws Exception { assertEquals(DIR_ONE_DATA_SIZE, duDir1.getSize()); } + @Test public void testDiskUsageDir() throws Exception { // dir level DU Response dirResponse = nsSummaryEndpoint.getDiskUsage(DIR_ONE_PATH, - false, false); + false, false, false); DUResponse duDirReponse = (DUResponse) dirResponse.getEntity(); assertEquals(3, duDirReponse.getCount()); List duSubDir = duDirReponse.getDuData(); @@ -517,21 +529,23 @@ public void testDiskUsageDir() throws Exception { assertEquals(KEY_SIX_SIZE, duDir4.getSize()); } + @Test public void testDiskUsageKey() throws Exception { // key level DU Response keyResponse = nsSummaryEndpoint.getDiskUsage(KEY_PATH, - false, false); + false, false, false); DUResponse keyObj = (DUResponse) keyResponse.getEntity(); assertEquals(0, keyObj.getCount()); assertEquals(KEY_FOUR_SIZE, keyObj.getSize()); } + @Test public void testDiskUsageUnknown() throws Exception { // invalid path check Response invalidResponse = nsSummaryEndpoint.getDiskUsage(INVALID_PATH, - false, false); + false, false, false); DUResponse invalidObj = (DUResponse) invalidResponse.getEntity(); assertEquals(ResponseStatus.PATH_NOT_FOUND, invalidObj.getStatus()); @@ -541,7 +555,7 @@ public void testDiskUsageUnknown() throws Exception { public void testDiskUsageWithReplication() throws Exception { setUpMultiBlockKey(); Response keyResponse = nsSummaryEndpoint.getDiskUsage(MULTI_BLOCK_KEY_PATH, - false, true); + false, true, false); DUResponse replicaDUResponse = (DUResponse) keyResponse.getEntity(); assertEquals(ResponseStatus.OK, replicaDUResponse.getStatus()); assertEquals(MULTI_BLOCK_KEY_SIZE_WITH_REPLICA, @@ -553,7 +567,7 @@ public void testDataSizeUnderRootWithReplication() throws IOException { setUpMultiBlockReplicatedKeys(); // withReplica is true Response rootResponse = nsSummaryEndpoint.getDiskUsage(ROOT_PATH, - false, true); + false, true, false); DUResponse replicaDUResponse = (DUResponse) rootResponse.getEntity(); assertEquals(ResponseStatus.OK, replicaDUResponse.getStatus()); assertEquals(MULTI_BLOCK_TOTAL_SIZE_WITH_REPLICA_UNDER_ROOT, @@ -567,7 +581,7 @@ public void testDataSizeUnderRootWithReplication() throws IOException { public void testDataSizeUnderVolWithReplication() throws IOException { setUpMultiBlockReplicatedKeys(); Response volResponse = nsSummaryEndpoint.getDiskUsage(VOL_PATH, - false, true); + false, true, false); DUResponse replicaDUResponse = (DUResponse) volResponse.getEntity(); assertEquals(ResponseStatus.OK, replicaDUResponse.getStatus()); assertEquals(MULTI_BLOCK_TOTAL_SIZE_WITH_REPLICA_UNDER_VOL, @@ -580,7 +594,7 @@ public void testDataSizeUnderVolWithReplication() throws IOException { public void testDataSizeUnderBucketWithReplication() throws IOException { setUpMultiBlockReplicatedKeys(); Response bucketResponse = nsSummaryEndpoint.getDiskUsage(BUCKET_ONE_PATH, - false, true); + false, true, false); DUResponse replicaDUResponse = (DUResponse) bucketResponse.getEntity(); assertEquals(ResponseStatus.OK, replicaDUResponse.getStatus()); assertEquals(MULTI_BLOCK_TOTAL_SIZE_WITH_REPLICA_UNDER_BUCKET1, @@ -599,7 +613,7 @@ public void testDataSizeUnderBucketWithReplication() throws IOException { public void testDataSizeUnderDirWithReplication() throws IOException { setUpMultiBlockReplicatedKeys(); Response dir1Response = nsSummaryEndpoint.getDiskUsage(DIR_ONE_PATH, - false, true); + false, true, false); DUResponse replicaDUResponse = (DUResponse) dir1Response.getEntity(); assertEquals(ResponseStatus.OK, replicaDUResponse.getStatus()); assertEquals(MULTI_BLOCK_TOTAL_SIZE_WITH_REPLICA_UNDER_DIR1, @@ -612,7 +626,7 @@ public void testDataSizeUnderDirWithReplication() throws IOException { public void testDataSizeUnderKeyWithReplication() throws IOException { setUpMultiBlockReplicatedKeys(); Response keyResponse = nsSummaryEndpoint.getDiskUsage(KEY_PATH, - false, true); + false, true, false); DUResponse replicaDUResponse = (DUResponse) keyResponse.getEntity(); assertEquals(ResponseStatus.OK, replicaDUResponse.getStatus()); assertEquals(MULTI_BLOCK_TOTAL_SIZE_WITH_REPLICA_UNDER_KEY, @@ -691,6 +705,151 @@ public void checkFileSizeDist(String path, int bin0, } } + @Test + public void testConstructFullPath() throws IOException { + OmKeyInfo keyInfo = new OmKeyInfo.Builder() + .setKeyName("file2") + .setVolumeName(VOL) + .setBucketName(BUCKET_ONE) + .setObjectID(KEY_TWO_OBJECT_ID) + .setParentObjectID(DIR_TWO_OBJECT_ID) + .build(); + // Call constructFullPath and verify the result + String fullPath = ReconUtils.constructFullPath(keyInfo, + reconNamespaceSummaryManager, reconOMMetadataManager); + String expectedPath = "vol/bucket1/dir1/dir2/file2"; + Assertions.assertEquals(expectedPath, fullPath); + + // Create key info for file 3 + keyInfo = new OmKeyInfo.Builder() + .setKeyName("file3") + .setVolumeName(VOL) + .setBucketName(BUCKET_ONE) + .setObjectID(KEY_THREE_OBJECT_ID) + .setParentObjectID(DIR_THREE_OBJECT_ID) + .build(); + fullPath = ReconUtils.constructFullPath(keyInfo, + reconNamespaceSummaryManager, reconOMMetadataManager); + expectedPath = "vol/bucket1/dir1/dir3/file3"; + Assertions.assertEquals(expectedPath, fullPath); + + // Create key info for file 6 + keyInfo = new OmKeyInfo.Builder() + .setKeyName("file6") + .setVolumeName(VOL) + .setBucketName(BUCKET_ONE) + .setObjectID(KEY_SIX_OBJECT_ID) + .setParentObjectID(DIR_FOUR_OBJECT_ID) + .build(); + fullPath = ReconUtils.constructFullPath(keyInfo, + reconNamespaceSummaryManager, reconOMMetadataManager); + expectedPath = "vol/bucket1/dir1/dir4/file6"; + Assertions.assertEquals(expectedPath, fullPath); + + // Create key info for file 1 + keyInfo = new OmKeyInfo.Builder() + .setKeyName("file1") + .setVolumeName(VOL) + .setBucketName(BUCKET_ONE) + .setObjectID(KEY_ONE_OBJECT_ID) + .setParentObjectID(BUCKET_ONE_OBJECT_ID) + .build(); + fullPath = ReconUtils.constructFullPath(keyInfo, + reconNamespaceSummaryManager, reconOMMetadataManager); + expectedPath = "vol/bucket1/file1"; + Assertions.assertEquals(expectedPath, fullPath); + + // Create key info for file 9 + keyInfo = new OmKeyInfo.Builder() + .setKeyName("file9") + .setVolumeName(VOL_TWO) + .setBucketName(BUCKET_THREE) + .setObjectID(KEY_NINE_OBJECT_ID) + .setParentObjectID(DIR_FIVE_OBJECT_ID) + .build(); + fullPath = ReconUtils.constructFullPath(keyInfo, + reconNamespaceSummaryManager, reconOMMetadataManager); + expectedPath = "vol2/bucket3/dir5/file9"; + Assertions.assertEquals(expectedPath, fullPath); + + // Check for when we encounter a NSSUmamry with parentId -1 + // Fetch NSSummary for dir1 and immediately update its parentId. + NSSummary dir1Summary = reconNamespaceSummaryManager.getNSSummary(DIR_ONE_OBJECT_ID); + dir1Summary.setParentId(-1); // Update parentId to -1 + + reconNamespaceSummaryManager.deleteNSSummary(DIR_ONE_OBJECT_ID); + reconNamespaceSummaryManager.storeNSSummary(DIR_ONE_OBJECT_ID, dir1Summary); + + NSSummary changedDir1Summary = reconNamespaceSummaryManager.getNSSummary(DIR_ONE_OBJECT_ID); + Assertions.assertEquals(-1, changedDir1Summary.getParentId(), "The parentId should be updated to -1"); + + keyInfo = new OmKeyInfo.Builder() + .setKeyName("file2") + .setVolumeName(VOL) + .setBucketName(BUCKET_ONE) + .setObjectID(KEY_TWO_OBJECT_ID) + .setParentObjectID(DIR_TWO_OBJECT_ID) + .build(); + // Call constructFullPath and verify the result + fullPath = ReconUtils.constructFullPath(keyInfo, + reconNamespaceSummaryManager, reconOMMetadataManager); + } + + @Test + public void testConstructFullPathWithNegativeParentIdTriggersRebuild() throws IOException { + // Setup + long dirOneObjectId = 1L; // Sample object ID for the directory + ReconNamespaceSummaryManager mockSummaryManager = mock(ReconNamespaceSummaryManager.class); + ReconOMMetadataManager mockMetadataManager = mock(ReconOMMetadataManager.class); + NSSummary dir1Summary = new NSSummary(); + dir1Summary.setParentId(-1); // Simulate directory at the top of the tree + when(mockSummaryManager.getNSSummary(dirOneObjectId)).thenReturn(dir1Summary); + + OmKeyInfo keyInfo = new OmKeyInfo.Builder() + .setKeyName("file2") + .setVolumeName("vol") + .setBucketName("bucket1") + .setObjectID(2L) + .setParentObjectID(dirOneObjectId) + .build(); + + String result = ReconUtils.constructFullPath(keyInfo, mockSummaryManager, mockMetadataManager); + assertEquals("", result, "Expected an empty string return due to rebuild trigger"); + } + + @Test + public void testLoggingWhenParentIdIsNegative() throws IOException { + ReconNamespaceSummaryManager mockManager = + mock(ReconNamespaceSummaryManager.class); + Logger mockLogger = mock(Logger.class); + ReconUtils.setLogger(mockLogger); + + NSSummary mockSummary = new NSSummary(); + mockSummary.setParentId(-1); + when(mockManager.getNSSummary(anyLong())).thenReturn(mockSummary); + + OmKeyInfo keyInfo = new OmKeyInfo.Builder() + .setKeyName("testKey") + .setVolumeName("vol") + .setBucketName("bucket") + .setObjectID(1L) + .setParentObjectID(1L) + .build(); + + ReconUtils.constructFullPath(keyInfo, mockManager, null); + + // Assert + ArgumentCaptor logCaptor = ArgumentCaptor.forClass(String.class); + verify(mockLogger).warn(logCaptor.capture()); + String loggedMessage = logCaptor.getValue(); + + // Here we can assert the exact message we expect to see in the logs. + assertEquals( + "NSSummary tree is currently being rebuilt, returning empty string " + + "for path construction.", loggedMessage); + } + + /** * Write directories and keys info into OM DB. * @throws Exception @@ -1247,7 +1406,7 @@ private static BucketLayout getBucketLayout() { } private static SCMNodeStat getMockSCMRootStat() { - return new SCMNodeStat(ROOT_QUOTA, ROOT_DATA_SIZE, - ROOT_QUOTA - ROOT_DATA_SIZE); + return new SCMNodeStat(ROOT_QUOTA, ROOT_DATA_SIZE, + ROOT_QUOTA - ROOT_DATA_SIZE, 0, ROOT_QUOTA - ROOT_DATA_SIZE - 1); } } diff --git a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestNSSummaryEndpointWithLegacy.java b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestNSSummaryEndpointWithLegacy.java index b324bd6b4276..dba245ce8b80 100644 --- a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestNSSummaryEndpointWithLegacy.java +++ b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestNSSummaryEndpointWithLegacy.java @@ -36,6 +36,7 @@ import org.apache.hadoop.ozone.om.OMConfigKeys; import org.apache.hadoop.ozone.om.OMMetadataManager; import org.apache.hadoop.ozone.om.OmMetadataManagerImpl; +import org.apache.hadoop.ozone.om.helpers.OmKeyInfo; import org.apache.hadoop.ozone.om.helpers.BucketLayout; import org.apache.hadoop.ozone.om.helpers.OmVolumeArgs; import org.apache.hadoop.ozone.om.helpers.OmBucketInfo; @@ -43,6 +44,7 @@ import org.apache.hadoop.ozone.om.helpers.OmKeyLocationInfoGroup; import org.apache.hadoop.ozone.recon.ReconConstants; import org.apache.hadoop.ozone.recon.ReconTestInjector; +import org.apache.hadoop.ozone.recon.ReconUtils; import org.apache.hadoop.ozone.recon.api.handlers.BucketHandler; import org.apache.hadoop.ozone.recon.api.handlers.EntityHandler; import org.apache.hadoop.ozone.recon.api.types.DUResponse; @@ -58,6 +60,7 @@ import org.apache.hadoop.ozone.recon.spi.impl.OzoneManagerServiceProviderImpl; import org.apache.hadoop.ozone.recon.spi.impl.StorageContainerServiceProviderImpl; import org.apache.hadoop.ozone.recon.tasks.NSSummaryTaskWithLegacy; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -74,15 +77,17 @@ import java.util.Set; import java.util.HashSet; +import static org.apache.hadoop.ozone.recon.OMMetadataManagerTestUtils.getTestReconOmMetadataManager; +import static org.apache.hadoop.ozone.recon.OMMetadataManagerTestUtils.getMockOzoneManagerServiceProvider; +import static org.apache.hadoop.ozone.recon.OMMetadataManagerTestUtils.writeDirToOm; +import static org.apache.hadoop.ozone.recon.OMMetadataManagerTestUtils.writeKeyToOm; +import static org.apache.hadoop.ozone.recon.OMMetadataManagerTestUtils.setConfiguration; + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.apache.hadoop.hdds.protocol.MockDatanodeDetails.randomDatanodeDetails; import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_DB_DIRS; import static org.apache.hadoop.ozone.OzoneConsts.OM_KEY_PREFIX; -import static org.apache.hadoop.ozone.recon.OMMetadataManagerTestUtils.writeKeyToOm; -import static org.apache.hadoop.ozone.recon.OMMetadataManagerTestUtils.writeDirToOm; -import static org.apache.hadoop.ozone.recon.OMMetadataManagerTestUtils.getMockOzoneManagerServiceProvider; -import static org.apache.hadoop.ozone.recon.OMMetadataManagerTestUtils.getTestReconOmMetadataManager; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -113,6 +118,7 @@ public class TestNSSummaryEndpointWithLegacy { @TempDir private Path temporaryFolder; + private ReconNamespaceSummaryManager reconNamespaceSummaryManager; private ReconOMMetadataManager reconOMMetadataManager; private NSSummaryEndpoint nsSummaryEndpoint; private OzoneConfiguration conf; @@ -243,10 +249,10 @@ public class TestNSSummaryEndpointWithLegacy { StandaloneReplicationConfig.getInstance(ONE)); private static final long FILE6_SIZE_WITH_REPLICA = getReplicatedSize(KEY_SIX_SIZE, - StandaloneReplicationConfig.getInstance(ONE));; + StandaloneReplicationConfig.getInstance(ONE)); private static final long FILE7_SIZE_WITH_REPLICA = getReplicatedSize(KEY_SEVEN_SIZE, - StandaloneReplicationConfig.getInstance(ONE));; + StandaloneReplicationConfig.getInstance(ONE)); private static final long FILE8_SIZE_WITH_REPLICA = getReplicatedSize(KEY_EIGHT_SIZE, StandaloneReplicationConfig.getInstance(ONE)); @@ -376,7 +382,7 @@ public void setUp() throws Exception { mock(StorageContainerServiceProviderImpl.class)) .addBinding(NSSummaryEndpoint.class) .build(); - ReconNamespaceSummaryManager reconNamespaceSummaryManager = + this.reconNamespaceSummaryManager = reconTestInjector.getInstance(ReconNamespaceSummaryManager.class); nsSummaryEndpoint = reconTestInjector.getInstance(NSSummaryEndpoint.class); @@ -449,7 +455,7 @@ public void testGetBasicInfoKey() throws Exception { public void testDiskUsageRoot() throws Exception { // root level DU Response rootResponse = nsSummaryEndpoint.getDiskUsage(ROOT_PATH, - false, false); + false, false, false); DUResponse duRootRes = (DUResponse) rootResponse.getEntity(); assertEquals(2, duRootRes.getCount()); List duRootData = duRootRes.getDuData(); @@ -468,7 +474,7 @@ public void testDiskUsageRoot() throws Exception { public void testDiskUsageVolume() throws Exception { // volume level DU Response volResponse = nsSummaryEndpoint.getDiskUsage(VOL_PATH, - false, false); + false, false, false); DUResponse duVolRes = (DUResponse) volResponse.getEntity(); assertEquals(2, duVolRes.getCount()); List duData = duVolRes.getDuData(); @@ -487,7 +493,7 @@ public void testDiskUsageVolume() throws Exception { public void testDiskUsageBucket() throws Exception { // bucket level DU Response bucketResponse = nsSummaryEndpoint.getDiskUsage(BUCKET_ONE_PATH, - false, false); + false, false, false); DUResponse duBucketResponse = (DUResponse) bucketResponse.getEntity(); assertEquals(1, duBucketResponse.getCount()); DUResponse.DiskUsage duDir1 = duBucketResponse.getDuData().get(0); @@ -499,7 +505,7 @@ public void testDiskUsageBucket() throws Exception { public void testDiskUsageDir() throws Exception { // dir level DU Response dirResponse = nsSummaryEndpoint.getDiskUsage(DIR_ONE_PATH, - false, false); + false, false, false); DUResponse duDirReponse = (DUResponse) dirResponse.getEntity(); assertEquals(3, duDirReponse.getCount()); List duSubDir = duDirReponse.getDuData(); @@ -522,7 +528,7 @@ public void testDiskUsageDir() throws Exception { public void testDiskUsageKey() throws Exception { // key level DU Response keyResponse = nsSummaryEndpoint.getDiskUsage(KEY_PATH, - false, false); + false, false, false); DUResponse keyObj = (DUResponse) keyResponse.getEntity(); assertEquals(0, keyObj.getCount()); assertEquals(KEY_FOUR_SIZE, keyObj.getSize()); @@ -532,7 +538,7 @@ public void testDiskUsageKey() throws Exception { public void testDiskUsageUnknown() throws Exception { // invalid path check Response invalidResponse = nsSummaryEndpoint.getDiskUsage(INVALID_PATH, - false, false); + false, false, false); DUResponse invalidObj = (DUResponse) invalidResponse.getEntity(); assertEquals(ResponseStatus.PATH_NOT_FOUND, invalidObj.getStatus()); @@ -542,7 +548,7 @@ public void testDiskUsageUnknown() throws Exception { public void testDiskUsageWithReplication() throws Exception { setUpMultiBlockKey(); Response keyResponse = nsSummaryEndpoint.getDiskUsage(MULTI_BLOCK_KEY_PATH, - false, true); + false, true, false); DUResponse replicaDUResponse = (DUResponse) keyResponse.getEntity(); assertEquals(ResponseStatus.OK, replicaDUResponse.getStatus()); assertEquals(MULTI_BLOCK_KEY_SIZE_WITH_REPLICA, @@ -554,7 +560,7 @@ public void testDataSizeUnderRootWithReplication() throws IOException { setUpMultiBlockReplicatedKeys(); // withReplica is true Response rootResponse = nsSummaryEndpoint.getDiskUsage(ROOT_PATH, - false, true); + false, true, false); DUResponse replicaDUResponse = (DUResponse) rootResponse.getEntity(); assertEquals(ResponseStatus.OK, replicaDUResponse.getStatus()); assertEquals(MULTI_BLOCK_TOTAL_SIZE_WITH_REPLICA_UNDER_ROOT, @@ -568,7 +574,7 @@ public void testDataSizeUnderRootWithReplication() throws IOException { public void testDataSizeUnderVolWithReplication() throws IOException { setUpMultiBlockReplicatedKeys(); Response volResponse = nsSummaryEndpoint.getDiskUsage(VOL_PATH, - false, true); + false, true, false); DUResponse replicaDUResponse = (DUResponse) volResponse.getEntity(); assertEquals(ResponseStatus.OK, replicaDUResponse.getStatus()); assertEquals(MULTI_BLOCK_TOTAL_SIZE_WITH_REPLICA_UNDER_VOL, @@ -581,7 +587,7 @@ public void testDataSizeUnderVolWithReplication() throws IOException { public void testDataSizeUnderBucketWithReplication() throws IOException { setUpMultiBlockReplicatedKeys(); Response bucketResponse = nsSummaryEndpoint.getDiskUsage(BUCKET_ONE_PATH, - false, true); + false, true, false); DUResponse replicaDUResponse = (DUResponse) bucketResponse.getEntity(); assertEquals(ResponseStatus.OK, replicaDUResponse.getStatus()); assertEquals(MULTI_BLOCK_TOTAL_SIZE_WITH_REPLICA_UNDER_BUCKET1, @@ -600,7 +606,7 @@ public void testDataSizeUnderBucketWithReplication() throws IOException { public void testDataSizeUnderDirWithReplication() throws IOException { setUpMultiBlockReplicatedKeys(); Response dir1Response = nsSummaryEndpoint.getDiskUsage(DIR_ONE_PATH, - false, true); + false, true, false); DUResponse replicaDUResponse = (DUResponse) dir1Response.getEntity(); assertEquals(ResponseStatus.OK, replicaDUResponse.getStatus()); assertEquals(MULTI_BLOCK_TOTAL_SIZE_WITH_REPLICA_UNDER_DIR1, @@ -613,7 +619,7 @@ public void testDataSizeUnderDirWithReplication() throws IOException { public void testDataSizeUnderKeyWithReplication() throws IOException { setUpMultiBlockReplicatedKeys(); Response keyResponse = nsSummaryEndpoint.getDiskUsage(KEY_PATH, - false, true); + false, true, false); DUResponse replicaDUResponse = (DUResponse) keyResponse.getEntity(); assertEquals(ResponseStatus.OK, replicaDUResponse.getStatus()); assertEquals(MULTI_BLOCK_TOTAL_SIZE_WITH_REPLICA_UNDER_KEY, @@ -692,6 +698,48 @@ public void checkFileSizeDist(String path, int bin0, } } + @Test + public void testConstructFullPath() throws IOException { + // For Key Tables the parent object ID is not set hence it + // will by default be set as -1 when the NSSummary object is created + OmKeyInfo keyInfo = new OmKeyInfo.Builder() + .setKeyName("dir1/dir2/file2") + .setVolumeName(VOL) + .setBucketName(BUCKET_ONE) + .setObjectID(KEY_TWO_OBJECT_ID) + .build(); + // Call constructFullPath and verify the result + String fullPath = ReconUtils.constructFullPath(keyInfo, + reconNamespaceSummaryManager, reconOMMetadataManager); + String expectedPath = "vol/bucket1/dir1/dir2/file2"; + Assertions.assertEquals(expectedPath, fullPath); + + // Create key info for file 3 + keyInfo = new OmKeyInfo.Builder() + .setKeyName("dir1/dir2/") + .setVolumeName(VOL) + .setBucketName(BUCKET_ONE) + .setObjectID(DIR_TWO_OBJECT_ID) + .build(); + fullPath = ReconUtils.constructFullPath(keyInfo, + reconNamespaceSummaryManager, reconOMMetadataManager); + expectedPath = "vol/bucket1/dir1/dir2/"; + Assertions.assertEquals(expectedPath, fullPath); + + // Create key info for file 6 + keyInfo = new OmKeyInfo.Builder() + .setKeyName("dir1/dir4/file6") + .setVolumeName(VOL) + .setBucketName(BUCKET_ONE) + .setObjectID(KEY_SIX_OBJECT_ID) + .build(); + fullPath = ReconUtils.constructFullPath(keyInfo, + reconNamespaceSummaryManager, reconOMMetadataManager); + expectedPath = "vol/bucket1/dir1/dir4/file6"; + Assertions.assertEquals(expectedPath, fullPath); + } + + /** * Write directories and keys info into OM DB. * @throws Exception @@ -875,6 +923,7 @@ private static OMMetadataManager initializeNewOmMetadataManager( omDbDir.getAbsolutePath()); omConfiguration.set(OMConfigKeys .OZONE_OM_ENABLE_FILESYSTEM_PATHS, "true"); + setConfiguration(omConfiguration); OMMetadataManager omMetadataManager = new OmMetadataManagerImpl( omConfiguration, null); @@ -1286,6 +1335,6 @@ private static BucketLayout getBucketLayout() { private static SCMNodeStat getMockSCMRootStat() { return new SCMNodeStat(ROOT_QUOTA, ROOT_DATA_SIZE, - ROOT_QUOTA - ROOT_DATA_SIZE); + ROOT_QUOTA - ROOT_DATA_SIZE, 0, ROOT_QUOTA - ROOT_DATA_SIZE - 1); } } diff --git a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestNSSummaryEndpointWithOBSAndLegacy.java b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestNSSummaryEndpointWithOBSAndLegacy.java new file mode 100644 index 000000000000..6a2f2c557db8 --- /dev/null +++ b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestNSSummaryEndpointWithOBSAndLegacy.java @@ -0,0 +1,1472 @@ +/* + * 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.ozone.recon.api; + +import static org.apache.hadoop.hdds.protocol.proto.HddsProtos.ReplicationFactor.ONE; +import static org.apache.hadoop.ozone.om.helpers.QuotaUtil.getReplicatedSize; + +import org.apache.hadoop.hdds.client.BlockID; +import org.apache.hadoop.hdds.client.StandaloneReplicationConfig; +import org.apache.hadoop.hdds.conf.OzoneConfiguration; +import org.apache.hadoop.hdds.protocol.DatanodeDetails; +import org.apache.hadoop.hdds.protocol.StorageType; +import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos; +import org.apache.hadoop.hdds.scm.container.ContainerID; +import org.apache.hadoop.hdds.scm.container.ContainerManager; +import org.apache.hadoop.hdds.scm.container.ContainerNotFoundException; +import org.apache.hadoop.hdds.scm.container.ContainerReplica; +import org.apache.hadoop.hdds.scm.container.placement.metrics.SCMNodeStat; +import org.apache.hadoop.hdds.scm.server.OzoneStorageContainerManager; +import org.apache.hadoop.ozone.OmUtils; +import org.apache.hadoop.ozone.OzoneConsts; +import org.apache.hadoop.ozone.om.OMConfigKeys; +import org.apache.hadoop.ozone.om.OMMetadataManager; +import org.apache.hadoop.ozone.om.OmMetadataManagerImpl; +import org.apache.hadoop.ozone.om.helpers.OmKeyInfo; +import org.apache.hadoop.ozone.om.helpers.OmBucketInfo; +import org.apache.hadoop.ozone.om.helpers.OmKeyLocationInfo; +import org.apache.hadoop.ozone.om.helpers.OmKeyLocationInfoGroup; +import org.apache.hadoop.ozone.om.helpers.BucketLayout; +import org.apache.hadoop.ozone.om.helpers.OmVolumeArgs; +import org.apache.hadoop.ozone.recon.ReconConstants; +import org.apache.hadoop.ozone.recon.ReconTestInjector; +import org.apache.hadoop.ozone.recon.ReconUtils; +import org.apache.hadoop.ozone.recon.api.handlers.BucketHandler; +import org.apache.hadoop.ozone.recon.api.handlers.EntityHandler; +import org.apache.hadoop.ozone.recon.api.types.BucketObjectDBInfo; +import org.apache.hadoop.ozone.recon.api.types.DUResponse; +import org.apache.hadoop.ozone.recon.api.types.EntityType; +import org.apache.hadoop.ozone.recon.api.types.NamespaceSummaryResponse; +import org.apache.hadoop.ozone.recon.api.types.QuotaUsageResponse; +import org.apache.hadoop.ozone.recon.api.types.ResponseStatus; +import org.apache.hadoop.ozone.recon.api.types.VolumeObjectDBInfo; +import org.apache.hadoop.ozone.recon.api.types.FileSizeDistributionResponse; +import org.apache.hadoop.ozone.recon.common.CommonUtils; +import org.apache.hadoop.ozone.recon.recovery.ReconOMMetadataManager; +import org.apache.hadoop.ozone.recon.scm.ReconNodeManager; +import org.apache.hadoop.ozone.recon.scm.ReconStorageContainerManagerFacade; +import org.apache.hadoop.ozone.recon.spi.ReconNamespaceSummaryManager; +import org.apache.hadoop.ozone.recon.spi.StorageContainerServiceProvider; +import org.apache.hadoop.ozone.recon.spi.impl.OzoneManagerServiceProviderImpl; +import org.apache.hadoop.ozone.recon.spi.impl.StorageContainerServiceProviderImpl; +import org.apache.hadoop.ozone.recon.tasks.NSSummaryTaskWithLegacy; +import org.apache.hadoop.ozone.recon.tasks.NSSummaryTaskWithOBS; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import javax.ws.rs.core.Response; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.ArrayList; +import java.util.Set; +import java.util.HashSet; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.apache.hadoop.hdds.protocol.MockDatanodeDetails.randomDatanodeDetails; +import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_DB_DIRS; +import static org.apache.hadoop.ozone.recon.OMMetadataManagerTestUtils.writeKeyToOm; +import static org.apache.hadoop.ozone.recon.OMMetadataManagerTestUtils.getMockOzoneManagerServiceProvider; +import static org.apache.hadoop.ozone.recon.OMMetadataManagerTestUtils.getTestReconOmMetadataManager; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Tests the NSSummary REST APIs within the context of an Object Store (OBS) layout, + * as well as Legacy layout buckets with FileSystemPaths disabled. The tests aim to + * validate API responses for buckets that follow the flat hierarchy model typical + * of OBS layouts. + *

+ * The test environment simulates a simple object storage structure with volumes + * containing buckets, which in turn contain files. Specifically, it includes: + * - Two OBS layout buckets (bucket1 and bucket2) under 'vol', each containing + * multiple files. + * - Two Legacy layout buckets (bucket3 and bucket4) under 'vol2', with 'bucket4' + * the fileSystemEnabled flag set to false for these legacy buckets. + *

+ * The directory structure for testing is as follows: + * . + * └── vol + * ├── bucket1 (OBS) + * │ ├── KEY_ONE + * │ ├── KEY_TWO + * │ └── KEY_THREE + * └── bucket2 (OBS) + * ├── KEY_FOUR + * └── KEY_FIVE + * └── vol2 + * ├── bucket3 (Legacy) + * │ ├── KEY_EIGHT + * │ ├── KEY_NINE + * │ └── KEY_TEN + * └── bucket4 (Legacy) + * └── KEY_ELEVEN + */ +public class TestNSSummaryEndpointWithOBSAndLegacy { + @TempDir + private Path temporaryFolder; + + private ReconOMMetadataManager reconOMMetadataManager; + private ReconNamespaceSummaryManager reconNamespaceSummaryManager; + private NSSummaryEndpoint nsSummaryEndpoint; + private OzoneConfiguration conf; + private CommonUtils commonUtils; + + private static final String TEST_PATH_UTILITY = + "/vol1/buck1/a/b/c/d/e/file1.txt"; + private static final String PARENT_DIR = "vol1/buck1/a/b/c/d/e"; + private static final String[] TEST_NAMES = + new String[]{"vol1", "buck1", "a", "b", "c", "d", "e", "file1.txt"}; + private static final String TEST_KEY_NAMES = "a/b/c/d/e/file1.txt"; + + // Object names + private static final String VOL = "vol"; + private static final String VOL_TWO = "vol2"; + private static final String BUCKET_ONE = "bucket1"; + private static final String BUCKET_TWO = "bucket2"; + private static final String BUCKET_THREE = "bucket3"; + private static final String BUCKET_FOUR = "bucket4"; + private static final String KEY_ONE = "file1"; + private static final String KEY_TWO = "////file2"; + private static final String KEY_THREE = "file3///"; + private static final String KEY_FOUR = "file4"; + private static final String KEY_FIVE = "_//////"; + private static final String KEY_EIGHT = "file8"; + private static final String KEY_NINE = "//////"; + private static final String KEY_TEN = "///__file10"; + private static final String KEY_ELEVEN = "////file11"; + private static final String MULTI_BLOCK_FILE = KEY_THREE; + + private static final long PARENT_OBJECT_ID_ZERO = 0L; + private static final long VOL_OBJECT_ID = 0L; + private static final long VOL_TWO_OBJECT_ID = 14L; + private static final long BUCKET_ONE_OBJECT_ID = 1L; + private static final long BUCKET_TWO_OBJECT_ID = 2L; + private static final long BUCKET_THREE_OBJECT_ID = 15L; + private static final long BUCKET_FOUR_OBJECT_ID = 16L; + private static final long KEY_ONE_OBJECT_ID = 3L; + private static final long KEY_TWO_OBJECT_ID = 5L; + private static final long KEY_THREE_OBJECT_ID = 8L; + private static final long KEY_FOUR_OBJECT_ID = 6L; + private static final long KEY_FIVE_OBJECT_ID = 9L; + private static final long KEY_EIGHT_OBJECT_ID = 17L; + private static final long KEY_NINE_OBJECT_ID = 19L; + private static final long KEY_TEN_OBJECT_ID = 20L; + private static final long KEY_ELEVEN_OBJECT_ID = 21L; + private static final long MULTI_BLOCK_KEY_OBJECT_ID = 13L; + + // container IDs + private static final long CONTAINER_ONE_ID = 1L; + private static final long CONTAINER_TWO_ID = 2L; + private static final long CONTAINER_THREE_ID = 3L; + private static final long CONTAINER_FOUR_ID = 4L; + private static final long CONTAINER_FIVE_ID = 5L; + private static final long CONTAINER_SIX_ID = 6L; + + // replication factors + private static final int CONTAINER_ONE_REPLICA_COUNT = 3; + private static final int CONTAINER_TWO_REPLICA_COUNT = 2; + private static final int CONTAINER_THREE_REPLICA_COUNT = 4; + private static final int CONTAINER_FOUR_REPLICA_COUNT = 5; + private static final int CONTAINER_FIVE_REPLICA_COUNT = 2; + private static final int CONTAINER_SIX_REPLICA_COUNT = 3; + + // block lengths + private static final long BLOCK_ONE_LENGTH = 1000L; + private static final long BLOCK_TWO_LENGTH = 2000L; + private static final long BLOCK_THREE_LENGTH = 3000L; + private static final long BLOCK_FOUR_LENGTH = 4000L; + private static final long BLOCK_FIVE_LENGTH = 5000L; + private static final long BLOCK_SIX_LENGTH = 6000L; + + // data size in bytes + private static final long FILE_ONE_SIZE = 500L; // bin 0 + private static final long FILE_TWO_SIZE = OzoneConsts.KB + 1; // bin 1 + private static final long FILE_THREE_SIZE = 4 * OzoneConsts.KB + 1; // bin 3 + private static final long FILE_FOUR_SIZE = 2 * OzoneConsts.KB + 1; // bin 2 + private static final long FILE_FIVE_SIZE = 100L; // bin 0 + private static final long FILE_EIGHT_SIZE = OzoneConsts.KB + 1; // bin 1 + private static final long FILE_NINE_SIZE = 2 * OzoneConsts.KB + 1; // bin 2 + private static final long FILE_TEN_SIZE = 2 * OzoneConsts.KB + 1; // bin 2 + private static final long FILE_ELEVEN_SIZE = OzoneConsts.KB + 1; // bin 1 + + private static final long FILE1_SIZE_WITH_REPLICA = + getReplicatedSize(FILE_ONE_SIZE, + StandaloneReplicationConfig.getInstance(ONE)); + private static final long FILE2_SIZE_WITH_REPLICA = + getReplicatedSize(FILE_TWO_SIZE, + StandaloneReplicationConfig.getInstance(ONE)); + private static final long FILE3_SIZE_WITH_REPLICA = + getReplicatedSize(FILE_THREE_SIZE, + StandaloneReplicationConfig.getInstance(ONE)); + private static final long FILE4_SIZE_WITH_REPLICA = + getReplicatedSize(FILE_FOUR_SIZE, + StandaloneReplicationConfig.getInstance(ONE)); + private static final long FILE5_SIZE_WITH_REPLICA = + getReplicatedSize(FILE_FIVE_SIZE, + StandaloneReplicationConfig.getInstance(ONE)); + + private static final long FILE8_SIZE_WITH_REPLICA = + getReplicatedSize(FILE_EIGHT_SIZE, + StandaloneReplicationConfig.getInstance(ONE)); + private static final long FILE9_SIZE_WITH_REPLICA = + getReplicatedSize(FILE_NINE_SIZE, + StandaloneReplicationConfig.getInstance(ONE)); + private static final long FILE10_SIZE_WITH_REPLICA = + getReplicatedSize(FILE_TEN_SIZE, + StandaloneReplicationConfig.getInstance(ONE)); + private static final long FILE11_SIZE_WITH_REPLICA = + getReplicatedSize(FILE_ELEVEN_SIZE, + StandaloneReplicationConfig.getInstance(ONE)); + + private static final long MULTI_BLOCK_KEY_SIZE_WITH_REPLICA + = FILE3_SIZE_WITH_REPLICA; + private static final long + MULTI_BLOCK_TOTAL_SIZE_WITH_REPLICA_UNDER_ROOT + = FILE1_SIZE_WITH_REPLICA + + FILE2_SIZE_WITH_REPLICA + + FILE3_SIZE_WITH_REPLICA + + FILE4_SIZE_WITH_REPLICA + + FILE5_SIZE_WITH_REPLICA + + FILE8_SIZE_WITH_REPLICA + + FILE9_SIZE_WITH_REPLICA + + FILE10_SIZE_WITH_REPLICA + + FILE11_SIZE_WITH_REPLICA; + + private static final long + MULTI_BLOCK_TOTAL_SIZE_WITH_REPLICA_UNDER_VOL + = FILE1_SIZE_WITH_REPLICA + + FILE2_SIZE_WITH_REPLICA + + FILE3_SIZE_WITH_REPLICA + + FILE4_SIZE_WITH_REPLICA + + FILE5_SIZE_WITH_REPLICA; + + private static final long + MULTI_BLOCK_TOTAL_SIZE_WITH_REPLICA_UNDER_BUCKET1 + = FILE1_SIZE_WITH_REPLICA + + FILE2_SIZE_WITH_REPLICA + + FILE3_SIZE_WITH_REPLICA; + + private static final long + MULTI_BLOCK_TOTAL_SIZE_WITH_REPLICA_UNDER_BUCKET3 + = FILE8_SIZE_WITH_REPLICA + + FILE9_SIZE_WITH_REPLICA + + FILE10_SIZE_WITH_REPLICA; + + + private static final long + MULTI_BLOCK_TOTAL_SIZE_WITH_REPLICA_UNDER_KEY + = FILE4_SIZE_WITH_REPLICA; + + // quota in bytes + private static final long ROOT_QUOTA = 2 * (2 * OzoneConsts.MB); + private static final long VOL_QUOTA = 2 * OzoneConsts.MB; + private static final long VOL_TWO_QUOTA = 2 * OzoneConsts.MB; + private static final long BUCKET_ONE_QUOTA = OzoneConsts.MB; + private static final long BUCKET_TWO_QUOTA = OzoneConsts.MB; + private static final long BUCKET_THREE_QUOTA = OzoneConsts.MB; + private static final long BUCKET_FOUR_QUOTA = OzoneConsts.MB; + + // mock client's path requests + private static final String TEST_USER = "TestUser"; + private static final String ROOT_PATH = "/"; + private static final String VOL_PATH = ROOT_PATH + VOL; + private static final String VOL_TWO_PATH = ROOT_PATH + VOL_TWO; + private static final String BUCKET_ONE_PATH = + ROOT_PATH + VOL + ROOT_PATH + BUCKET_ONE; + private static final String BUCKET_TWO_PATH = + ROOT_PATH + VOL + ROOT_PATH + BUCKET_TWO; + private static final String BUCKET_THREE_PATH = + ROOT_PATH + VOL_TWO + ROOT_PATH + BUCKET_THREE; + private static final String BUCKET_FOUR_PATH = + ROOT_PATH + VOL_TWO + ROOT_PATH + BUCKET_FOUR; + private static final String KEY_ONE_PATH = + ROOT_PATH + VOL + ROOT_PATH + BUCKET_ONE + ROOT_PATH + KEY_ONE; + private static final String KEY_TWO_PATH = + ROOT_PATH + VOL + ROOT_PATH + BUCKET_ONE + ROOT_PATH + KEY_TWO; + private static final String KEY_THREE_PATH = + ROOT_PATH + VOL + ROOT_PATH + BUCKET_ONE + ROOT_PATH + KEY_THREE; + private static final String KEY_FOUR_PATH = + ROOT_PATH + VOL + ROOT_PATH + BUCKET_TWO + ROOT_PATH + KEY_FOUR; + private static final String KEY_FIVE_PATH = + ROOT_PATH + VOL + ROOT_PATH + BUCKET_TWO + ROOT_PATH + KEY_FIVE; + private static final String KEY_EIGHT_PATH = + ROOT_PATH + VOL_TWO + ROOT_PATH + BUCKET_THREE + ROOT_PATH + KEY_EIGHT; + private static final String KEY_NINE_PATH = + ROOT_PATH + VOL_TWO + ROOT_PATH + BUCKET_THREE + ROOT_PATH + KEY_NINE; + private static final String KEY_TEN_PATH = + ROOT_PATH + VOL_TWO + ROOT_PATH + BUCKET_THREE + ROOT_PATH + KEY_TEN; + private static final String KEY_ELEVEN_PATH = + ROOT_PATH + VOL_TWO + ROOT_PATH + BUCKET_FOUR + ROOT_PATH + KEY_ELEVEN; + private static final String KEY4_PATH = + ROOT_PATH + VOL + ROOT_PATH + BUCKET_TWO + ROOT_PATH + KEY_FOUR; + private static final String MULTI_BLOCK_KEY_PATH = + ROOT_PATH + VOL + ROOT_PATH + BUCKET_ONE + ROOT_PATH + KEY_THREE; + private static final String INVALID_PATH = "/vol/path/not/found"; + + // some expected answers + private static final long ROOT_DATA_SIZE = + FILE_ONE_SIZE + FILE_TWO_SIZE + FILE_THREE_SIZE + FILE_FOUR_SIZE + + FILE_FIVE_SIZE + FILE_EIGHT_SIZE + FILE_NINE_SIZE + FILE_TEN_SIZE + + FILE_ELEVEN_SIZE; + private static final long VOL_DATA_SIZE = FILE_ONE_SIZE + FILE_TWO_SIZE + + FILE_THREE_SIZE + FILE_FOUR_SIZE + FILE_FIVE_SIZE; + + private static final long VOL_TWO_DATA_SIZE = + FILE_EIGHT_SIZE + FILE_NINE_SIZE + FILE_TEN_SIZE + FILE_ELEVEN_SIZE; + + private static final long BUCKET_ONE_DATA_SIZE = FILE_ONE_SIZE + + FILE_TWO_SIZE + + FILE_THREE_SIZE; + + private static final long BUCKET_TWO_DATA_SIZE = + FILE_FOUR_SIZE + FILE_FIVE_SIZE; + + private static final long BUCKET_THREE_DATA_SIZE = + FILE_EIGHT_SIZE + FILE_NINE_SIZE + FILE_TEN_SIZE; + + private static final long BUCKET_FOUR_DATA_SIZE = FILE_ELEVEN_SIZE; + + + @BeforeEach + public void setUp() throws Exception { + conf = new OzoneConfiguration(); + // By setting this config our Legacy buckets will behave like OBS buckets. + conf.set(OMConfigKeys.OZONE_OM_ENABLE_FILESYSTEM_PATHS, "false"); + OMMetadataManager omMetadataManager = initializeNewOmMetadataManager( + Files.createDirectory(temporaryFolder.resolve( + "JunitOmDBDir")).toFile(), conf); + OzoneManagerServiceProviderImpl ozoneManagerServiceProvider = + getMockOzoneManagerServiceProvider(); + reconOMMetadataManager = getTestReconOmMetadataManager(omMetadataManager, + Files.createDirectory(temporaryFolder.resolve( + "omMetadatDir")).toFile()); + + ReconTestInjector reconTestInjector = + new ReconTestInjector.Builder(temporaryFolder.toFile()) + .withReconOm(reconOMMetadataManager) + .withOmServiceProvider(ozoneManagerServiceProvider) + .withReconSqlDb() + .withContainerDB() + .addBinding(OzoneStorageContainerManager.class, + getMockReconSCM()) + .addBinding(StorageContainerServiceProvider.class, + mock(StorageContainerServiceProviderImpl.class)) + .addBinding(NSSummaryEndpoint.class) + .build(); + reconNamespaceSummaryManager = + reconTestInjector.getInstance(ReconNamespaceSummaryManager.class); + nsSummaryEndpoint = reconTestInjector.getInstance(NSSummaryEndpoint.class); + + // populate OM DB and reprocess into Recon RocksDB + populateOMDB(); + NSSummaryTaskWithOBS nsSummaryTaskWithOBS = + new NSSummaryTaskWithOBS(reconNamespaceSummaryManager, + reconOMMetadataManager, conf); + nsSummaryTaskWithOBS.reprocessWithOBS(reconOMMetadataManager); + NSSummaryTaskWithLegacy nsSummaryTaskWithLegacy = + new NSSummaryTaskWithLegacy(reconNamespaceSummaryManager, + reconOMMetadataManager, conf); + nsSummaryTaskWithLegacy.reprocessWithLegacy(reconOMMetadataManager); + commonUtils = new CommonUtils(); + } + + @Test + public void testUtility() { + String[] names = EntityHandler.parseRequestPath(TEST_PATH_UTILITY); + assertArrayEquals(TEST_NAMES, names); + String keyName = BucketHandler.getKeyName(names); + assertEquals(TEST_KEY_NAMES, keyName); + String subpath = BucketHandler.buildSubpath(PARENT_DIR, "file1.txt"); + assertEquals(TEST_PATH_UTILITY, subpath); + } + + @Test + public void testGetBasicInfoRoot() throws Exception { + // Test root basics + Response rootResponse = nsSummaryEndpoint.getBasicInfo(ROOT_PATH); + NamespaceSummaryResponse rootResponseObj = + (NamespaceSummaryResponse) rootResponse.getEntity(); + assertEquals(EntityType.ROOT, rootResponseObj.getEntityType()); + assertEquals(2, rootResponseObj.getCountStats().getNumVolume()); + assertEquals(4, rootResponseObj.getCountStats().getNumBucket()); + assertEquals(9, rootResponseObj.getCountStats().getNumTotalKey()); + } + + @Test + public void testGetBasicInfoVol() throws Exception { + // Test volume basics + Response volResponse = nsSummaryEndpoint.getBasicInfo(VOL_PATH); + NamespaceSummaryResponse volResponseObj = + (NamespaceSummaryResponse) volResponse.getEntity(); + assertEquals(EntityType.VOLUME, + volResponseObj.getEntityType()); + assertEquals(2, volResponseObj.getCountStats().getNumBucket()); + assertEquals(5, volResponseObj.getCountStats().getNumTotalKey()); + assertEquals(TEST_USER, ((VolumeObjectDBInfo) volResponseObj. + getObjectDBInfo()).getAdmin()); + assertEquals(TEST_USER, ((VolumeObjectDBInfo) volResponseObj. + getObjectDBInfo()).getOwner()); + assertEquals(VOL, volResponseObj.getObjectDBInfo().getName()); + assertEquals(2097152, volResponseObj.getObjectDBInfo().getQuotaInBytes()); + assertEquals(-1, volResponseObj.getObjectDBInfo().getQuotaInNamespace()); + } + + @Test + public void testGetBasicInfoVolTwo() throws Exception { + // Test volume 2's basics + Response volTwoResponse = nsSummaryEndpoint.getBasicInfo(VOL_TWO_PATH); + NamespaceSummaryResponse volTwoResponseObj = + (NamespaceSummaryResponse) volTwoResponse.getEntity(); + assertEquals(EntityType.VOLUME, + volTwoResponseObj.getEntityType()); + assertEquals(2, volTwoResponseObj.getCountStats().getNumBucket()); + assertEquals(4, volTwoResponseObj.getCountStats().getNumTotalKey()); + assertEquals(TEST_USER, ((VolumeObjectDBInfo) volTwoResponseObj. + getObjectDBInfo()).getAdmin()); + assertEquals(TEST_USER, ((VolumeObjectDBInfo) volTwoResponseObj. + getObjectDBInfo()).getOwner()); + assertEquals(VOL_TWO, volTwoResponseObj.getObjectDBInfo().getName()); + assertEquals(2097152, + volTwoResponseObj.getObjectDBInfo().getQuotaInBytes()); + assertEquals(-1, volTwoResponseObj.getObjectDBInfo().getQuotaInNamespace()); + } + + @Test + public void testGetBasicInfoBucketOne() throws Exception { + // Test bucket 1's basics + Response bucketOneResponse = + nsSummaryEndpoint.getBasicInfo(BUCKET_ONE_PATH); + NamespaceSummaryResponse bucketOneObj = + (NamespaceSummaryResponse) bucketOneResponse.getEntity(); + assertEquals(EntityType.BUCKET, bucketOneObj.getEntityType()); + assertEquals(3, bucketOneObj.getCountStats().getNumTotalKey()); + assertEquals(VOL, + ((BucketObjectDBInfo) bucketOneObj.getObjectDBInfo()).getVolumeName()); + assertEquals(StorageType.DISK, + ((BucketObjectDBInfo) + bucketOneObj.getObjectDBInfo()).getStorageType()); + assertEquals(getOBSBucketLayout(), + ((BucketObjectDBInfo) + bucketOneObj.getObjectDBInfo()).getBucketLayout()); + assertEquals(BUCKET_ONE, + ((BucketObjectDBInfo) bucketOneObj.getObjectDBInfo()).getName()); + } + + @Test + public void testGetBasicInfoBucketTwo() throws Exception { + // Test bucket 2's basics + Response bucketTwoResponse = + nsSummaryEndpoint.getBasicInfo(BUCKET_TWO_PATH); + NamespaceSummaryResponse bucketTwoObj = + (NamespaceSummaryResponse) bucketTwoResponse.getEntity(); + assertEquals(EntityType.BUCKET, bucketTwoObj.getEntityType()); + assertEquals(2, bucketTwoObj.getCountStats().getNumTotalKey()); + assertEquals(VOL, + ((BucketObjectDBInfo) bucketTwoObj.getObjectDBInfo()).getVolumeName()); + assertEquals(StorageType.DISK, + ((BucketObjectDBInfo) + bucketTwoObj.getObjectDBInfo()).getStorageType()); + assertEquals(getOBSBucketLayout(), + ((BucketObjectDBInfo) + bucketTwoObj.getObjectDBInfo()).getBucketLayout()); + assertEquals(BUCKET_TWO, + ((BucketObjectDBInfo) bucketTwoObj.getObjectDBInfo()).getName()); + } + + @Test + public void testGetBasicInfoBucketThree() throws Exception { + // Test bucket 3's basics + Response bucketThreeResponse = + nsSummaryEndpoint.getBasicInfo(BUCKET_THREE_PATH); + NamespaceSummaryResponse bucketThreeObj = (NamespaceSummaryResponse) + bucketThreeResponse.getEntity(); + assertEquals(EntityType.BUCKET, bucketThreeObj.getEntityType()); + assertEquals(3, bucketThreeObj.getCountStats().getNumTotalKey()); + assertEquals(VOL_TWO, + ((BucketObjectDBInfo) bucketThreeObj.getObjectDBInfo()).getVolumeName()); + assertEquals(StorageType.DISK, + ((BucketObjectDBInfo) + bucketThreeObj.getObjectDBInfo()).getStorageType()); + assertEquals(getLegacyBucketLayout(), + ((BucketObjectDBInfo) + bucketThreeObj.getObjectDBInfo()).getBucketLayout()); + assertEquals(BUCKET_THREE, + ((BucketObjectDBInfo) bucketThreeObj.getObjectDBInfo()).getName()); + } + + @Test + public void testGetBasicInfoBucketFour() throws Exception { + // Test bucket 4's basics + Response bucketFourResponse = + nsSummaryEndpoint.getBasicInfo(BUCKET_FOUR_PATH); + NamespaceSummaryResponse bucketFourObj = + (NamespaceSummaryResponse) bucketFourResponse.getEntity(); + assertEquals(EntityType.BUCKET, bucketFourObj.getEntityType()); + assertEquals(1, bucketFourObj.getCountStats().getNumTotalKey()); + assertEquals(VOL_TWO, + ((BucketObjectDBInfo) bucketFourObj.getObjectDBInfo()).getVolumeName()); + assertEquals(StorageType.DISK, + ((BucketObjectDBInfo) + bucketFourObj.getObjectDBInfo()).getStorageType()); + assertEquals(getLegacyBucketLayout(), + ((BucketObjectDBInfo) + bucketFourObj.getObjectDBInfo()).getBucketLayout()); + assertEquals(BUCKET_FOUR, + ((BucketObjectDBInfo) bucketFourObj.getObjectDBInfo()).getName()); + } + + @Test + public void testGetBasicInfoNoPath() throws Exception { + // Test invalid path + commonUtils.testNSSummaryBasicInfoNoPath(nsSummaryEndpoint); + } + + @Test + public void testGetBasicInfoKey() throws Exception { + // Test key + commonUtils.testNSSummaryBasicInfoKey(nsSummaryEndpoint); + } + + @Test + public void testDiskUsageRoot() throws Exception { + // root level DU + Response rootResponse = nsSummaryEndpoint.getDiskUsage(ROOT_PATH, + false, false, false); + DUResponse duRootRes = (DUResponse) rootResponse.getEntity(); + assertEquals(2, duRootRes.getCount()); + List duRootData = duRootRes.getDuData(); + // sort based on subpath + Collections.sort(duRootData, + Comparator.comparing(DUResponse.DiskUsage::getSubpath)); + DUResponse.DiskUsage duVol1 = duRootData.get(0); + DUResponse.DiskUsage duVol2 = duRootData.get(1); + assertEquals(VOL_PATH, duVol1.getSubpath()); + assertEquals(VOL_TWO_PATH, duVol2.getSubpath()); + assertEquals(VOL_DATA_SIZE, duVol1.getSize()); + assertEquals(VOL_TWO_DATA_SIZE, duVol2.getSize()); + } + + @Test + public void testDiskUsageVolume() throws Exception { + // volume level DU + Response volResponse = nsSummaryEndpoint.getDiskUsage(VOL_PATH, + false, false, false); + DUResponse duVolRes = (DUResponse) volResponse.getEntity(); + assertEquals(2, duVolRes.getCount()); + List duData = duVolRes.getDuData(); + // sort based on subpath + Collections.sort(duData, + Comparator.comparing(DUResponse.DiskUsage::getSubpath)); + DUResponse.DiskUsage duBucket1 = duData.get(0); + DUResponse.DiskUsage duBucket2 = duData.get(1); + assertEquals(BUCKET_ONE_PATH, duBucket1.getSubpath()); + assertEquals(BUCKET_TWO_PATH, duBucket2.getSubpath()); + assertEquals(BUCKET_ONE_DATA_SIZE, duBucket1.getSize()); + assertEquals(BUCKET_TWO_DATA_SIZE, duBucket2.getSize()); + } + + @Test + public void testDiskUsageVolTwo() throws Exception { + // volume level DU + Response volResponse = nsSummaryEndpoint.getDiskUsage(VOL_TWO_PATH, + false, false, false); + DUResponse duVolRes = (DUResponse) volResponse.getEntity(); + assertEquals(2, duVolRes.getCount()); + List duData = duVolRes.getDuData(); + // sort based on subpath + Collections.sort(duData, + Comparator.comparing(DUResponse.DiskUsage::getSubpath)); + DUResponse.DiskUsage duBucket3 = duData.get(0); + DUResponse.DiskUsage duBucket4 = duData.get(1); + assertEquals(BUCKET_THREE_PATH, duBucket3.getSubpath()); + assertEquals(BUCKET_FOUR_PATH, duBucket4.getSubpath()); + assertEquals(VOL_TWO_DATA_SIZE, duVolRes.getSize()); + } + + @Test + public void testDiskUsageBucketOne() throws Exception { + // bucket level DU + Response bucketResponse = nsSummaryEndpoint.getDiskUsage(BUCKET_ONE_PATH, + false, false, false); + DUResponse duBucketResponse = (DUResponse) bucketResponse.getEntity(); + // There are no sub-paths under this OBS bucket. + assertEquals(0, duBucketResponse.getCount()); + + Response bucketResponseWithSubpath = nsSummaryEndpoint.getDiskUsage( + BUCKET_ONE_PATH, true, false, false); + DUResponse duBucketResponseWithFiles = + (DUResponse) bucketResponseWithSubpath.getEntity(); + assertEquals(3, duBucketResponseWithFiles.getCount()); + + assertEquals(BUCKET_ONE_DATA_SIZE, duBucketResponse.getSize()); + } + + @Test + public void testDiskUsageBucketTwo() throws Exception { + // bucket level DU + Response bucketResponse = nsSummaryEndpoint.getDiskUsage(BUCKET_TWO_PATH, + false, false, false); + DUResponse duBucketResponse = (DUResponse) bucketResponse.getEntity(); + // There are no sub-paths under this OBS bucket. + assertEquals(0, duBucketResponse.getCount()); + + Response bucketResponseWithSubpath = nsSummaryEndpoint.getDiskUsage( + BUCKET_TWO_PATH, true, false, false); + DUResponse duBucketResponseWithFiles = + (DUResponse) bucketResponseWithSubpath.getEntity(); + assertEquals(2, duBucketResponseWithFiles.getCount()); + + assertEquals(BUCKET_TWO_DATA_SIZE, duBucketResponse.getSize()); + } + + @Test + public void testDiskUsageBucketThree() throws Exception { + // bucket level DU + Response bucketResponse = nsSummaryEndpoint.getDiskUsage(BUCKET_THREE_PATH, + false, false, false); + DUResponse duBucketResponse = (DUResponse) bucketResponse.getEntity(); + // There are no sub-paths under this Legacy bucket. + assertEquals(0, duBucketResponse.getCount()); + + Response bucketResponseWithSubpath = nsSummaryEndpoint.getDiskUsage( + BUCKET_THREE_PATH, true, false, false); + DUResponse duBucketResponseWithFiles = + (DUResponse) bucketResponseWithSubpath.getEntity(); + assertEquals(3, duBucketResponseWithFiles.getCount()); + + assertEquals(BUCKET_THREE_DATA_SIZE, duBucketResponse.getSize()); + } + + @Test + public void testDiskUsageKey1() throws Exception { + // key level DU + Response keyResponse = nsSummaryEndpoint.getDiskUsage(KEY_ONE_PATH, + false, false, false); + DUResponse duKeyResponse = (DUResponse) keyResponse.getEntity(); + assertEquals(0, duKeyResponse.getCount()); + assertEquals(FILE_ONE_SIZE, duKeyResponse.getSize()); + } + + @Test + public void testDiskUsageKey2() throws Exception { + // key level DU + Response keyResponse = nsSummaryEndpoint.getDiskUsage(KEY_TWO_PATH, + false, false, false); + DUResponse duKeyResponse = (DUResponse) keyResponse.getEntity(); + assertEquals(0, duKeyResponse.getCount()); + assertEquals(FILE_TWO_SIZE, duKeyResponse.getSize()); + } + + @Test + public void testDiskUsageKey4() throws Exception { + // key level DU + Response keyResponse = nsSummaryEndpoint.getDiskUsage(KEY4_PATH, + true, false, false); + DUResponse duKeyResponse = (DUResponse) keyResponse.getEntity(); + assertEquals(0, duKeyResponse.getCount()); + assertEquals(FILE_FOUR_SIZE, duKeyResponse.getSize()); + } + + @Test + public void testDiskUsageKey5() throws Exception { + // key level DU + Response keyResponse = nsSummaryEndpoint.getDiskUsage(KEY_FIVE_PATH, + false, false, false); + DUResponse duKeyResponse = (DUResponse) keyResponse.getEntity(); + assertEquals(0, duKeyResponse.getCount()); + assertEquals(FILE_FIVE_SIZE, duKeyResponse.getSize()); + } + + @Test + public void testDiskUsageKey8() throws Exception { + // key level DU + Response keyResponse = nsSummaryEndpoint.getDiskUsage(KEY_EIGHT_PATH, + false, false, false); + DUResponse duKeyResponse = (DUResponse) keyResponse.getEntity(); + assertEquals(0, duKeyResponse.getCount()); + assertEquals(FILE_EIGHT_SIZE, duKeyResponse.getSize()); + } + + @Test + public void testDiskUsageKey11() throws Exception { + // key level DU + Response keyResponse = nsSummaryEndpoint.getDiskUsage(KEY_ELEVEN_PATH, + false, false, false); + DUResponse duKeyResponse = (DUResponse) keyResponse.getEntity(); + assertEquals(0, duKeyResponse.getCount()); + assertEquals(FILE_ELEVEN_SIZE, duKeyResponse.getSize()); + } + + @Test + public void testDiskUsageUnknown() throws Exception { + // invalid path check + Response invalidResponse = nsSummaryEndpoint.getDiskUsage(INVALID_PATH, + false, false, false); + DUResponse invalidObj = (DUResponse) invalidResponse.getEntity(); + assertEquals(ResponseStatus.PATH_NOT_FOUND, + invalidObj.getStatus()); + } + + @Test + public void testDiskUsageWithReplication() throws Exception { + setUpMultiBlockKey(); + Response keyResponse = nsSummaryEndpoint.getDiskUsage(MULTI_BLOCK_KEY_PATH, + false, true, false); + DUResponse replicaDUResponse = (DUResponse) keyResponse.getEntity(); + assertEquals(ResponseStatus.OK, replicaDUResponse.getStatus()); + assertEquals(MULTI_BLOCK_KEY_SIZE_WITH_REPLICA, + replicaDUResponse.getSizeWithReplica()); + } + + @Test + public void testDataSizeUnderRootWithReplication() throws IOException { + setUpMultiBlockReplicatedKeys(); + // withReplica is true + Response rootResponse = nsSummaryEndpoint.getDiskUsage(ROOT_PATH, + false, true, false); + DUResponse replicaDUResponse = (DUResponse) rootResponse.getEntity(); + assertEquals(ResponseStatus.OK, replicaDUResponse.getStatus()); + assertEquals(MULTI_BLOCK_TOTAL_SIZE_WITH_REPLICA_UNDER_ROOT, + replicaDUResponse.getSizeWithReplica()); + assertEquals(MULTI_BLOCK_TOTAL_SIZE_WITH_REPLICA_UNDER_VOL, + replicaDUResponse.getDuData().get(0).getSizeWithReplica()); + + } + + @Test + public void testDataSizeUnderVolWithReplication() throws IOException { + setUpMultiBlockReplicatedKeys(); + Response volResponse = nsSummaryEndpoint.getDiskUsage(VOL_PATH, + false, true, false); + DUResponse replicaDUResponse = (DUResponse) volResponse.getEntity(); + assertEquals(ResponseStatus.OK, replicaDUResponse.getStatus()); + assertEquals(MULTI_BLOCK_TOTAL_SIZE_WITH_REPLICA_UNDER_VOL, + replicaDUResponse.getSizeWithReplica()); + assertEquals(MULTI_BLOCK_TOTAL_SIZE_WITH_REPLICA_UNDER_BUCKET1, + replicaDUResponse.getDuData().get(0).getSizeWithReplica()); + } + + @Test + public void testDataSizeUnderBucketOneWithReplication() throws IOException { + setUpMultiBlockReplicatedKeys(); + Response bucketResponse = nsSummaryEndpoint.getDiskUsage(BUCKET_ONE_PATH, + false, true, false); + DUResponse replicaDUResponse = (DUResponse) bucketResponse.getEntity(); + assertEquals(ResponseStatus.OK, replicaDUResponse.getStatus()); + assertEquals(MULTI_BLOCK_TOTAL_SIZE_WITH_REPLICA_UNDER_BUCKET1, + replicaDUResponse.getSizeWithReplica()); + } + + @Test + public void testDataSizeUnderBucketThreeWithReplication() throws IOException { + setUpMultiBlockReplicatedKeys(); + Response bucketResponse = nsSummaryEndpoint.getDiskUsage(BUCKET_THREE_PATH, + false, true, false); + DUResponse replicaDUResponse = (DUResponse) bucketResponse.getEntity(); + assertEquals(ResponseStatus.OK, replicaDUResponse.getStatus()); + assertEquals(MULTI_BLOCK_TOTAL_SIZE_WITH_REPLICA_UNDER_BUCKET3, + replicaDUResponse.getSizeWithReplica()); + } + + @Test + public void testDataSizeUnderKeyWithReplication() throws IOException { + setUpMultiBlockReplicatedKeys(); + Response keyResponse = nsSummaryEndpoint.getDiskUsage(KEY4_PATH, + false, true, false); + DUResponse replicaDUResponse = (DUResponse) keyResponse.getEntity(); + assertEquals(ResponseStatus.OK, replicaDUResponse.getStatus()); + assertEquals(MULTI_BLOCK_TOTAL_SIZE_WITH_REPLICA_UNDER_KEY, + replicaDUResponse.getSizeWithReplica()); + } + + @Test + public void testQuotaUsage() throws Exception { + // root level quota usage + Response rootResponse = nsSummaryEndpoint.getQuotaUsage(ROOT_PATH); + QuotaUsageResponse quRootRes = + (QuotaUsageResponse) rootResponse.getEntity(); + assertEquals(ROOT_QUOTA, quRootRes.getQuota()); + assertEquals(ROOT_DATA_SIZE, quRootRes.getQuotaUsed()); + + // volume level quota usage + Response volResponse = nsSummaryEndpoint.getQuotaUsage(VOL_PATH); + QuotaUsageResponse quVolRes = (QuotaUsageResponse) volResponse.getEntity(); + assertEquals(VOL_QUOTA, quVolRes.getQuota()); + assertEquals(VOL_DATA_SIZE, quVolRes.getQuotaUsed()); + + // bucket level quota usage + Response bucketRes = nsSummaryEndpoint.getQuotaUsage(BUCKET_ONE_PATH); + QuotaUsageResponse quBucketRes = (QuotaUsageResponse) bucketRes.getEntity(); + assertEquals(BUCKET_ONE_QUOTA, quBucketRes.getQuota()); + assertEquals(BUCKET_ONE_DATA_SIZE, quBucketRes.getQuotaUsed()); + + Response bucketRes2 = nsSummaryEndpoint.getQuotaUsage(BUCKET_TWO_PATH); + QuotaUsageResponse quBucketRes2 = + (QuotaUsageResponse) bucketRes2.getEntity(); + assertEquals(BUCKET_TWO_QUOTA, quBucketRes2.getQuota()); + assertEquals(BUCKET_TWO_DATA_SIZE, quBucketRes2.getQuotaUsed()); + + Response bucketRes3 = nsSummaryEndpoint.getQuotaUsage(BUCKET_THREE_PATH); + QuotaUsageResponse quBucketRes3 = + (QuotaUsageResponse) bucketRes3.getEntity(); + assertEquals(BUCKET_THREE_QUOTA, quBucketRes3.getQuota()); + assertEquals(BUCKET_THREE_DATA_SIZE, quBucketRes3.getQuotaUsed()); + + Response bucketRes4 = nsSummaryEndpoint.getQuotaUsage(BUCKET_FOUR_PATH); + QuotaUsageResponse quBucketRes4 = + (QuotaUsageResponse) bucketRes4.getEntity(); + assertEquals(BUCKET_FOUR_QUOTA, quBucketRes4.getQuota()); + assertEquals(BUCKET_FOUR_DATA_SIZE, quBucketRes4.getQuotaUsed()); + + // other level not applicable + Response naResponse2 = nsSummaryEndpoint.getQuotaUsage(KEY4_PATH); + QuotaUsageResponse quotaUsageResponse2 = + (QuotaUsageResponse) naResponse2.getEntity(); + assertEquals(ResponseStatus.TYPE_NOT_APPLICABLE, + quotaUsageResponse2.getResponseCode()); + + // invalid path request + Response invalidRes = nsSummaryEndpoint.getQuotaUsage(INVALID_PATH); + QuotaUsageResponse invalidResObj = + (QuotaUsageResponse) invalidRes.getEntity(); + assertEquals(ResponseStatus.PATH_NOT_FOUND, + invalidResObj.getResponseCode()); + } + + + @Test + public void testFileSizeDist() throws Exception { + checkFileSizeDist(ROOT_PATH, 2, 3, 3, 1); + checkFileSizeDist(VOL_PATH, 2, 1, 1, 1); + checkFileSizeDist(BUCKET_ONE_PATH, 1, 1, 0, 1); + } + + public void checkFileSizeDist(String path, int bin0, + int bin1, int bin2, int bin3) throws Exception { + Response res = nsSummaryEndpoint.getFileSizeDistribution(path); + FileSizeDistributionResponse fileSizeDistResObj = + (FileSizeDistributionResponse) res.getEntity(); + int[] fileSizeDist = fileSizeDistResObj.getFileSizeDist(); + assertEquals(bin0, fileSizeDist[0]); + assertEquals(bin1, fileSizeDist[1]); + assertEquals(bin2, fileSizeDist[2]); + assertEquals(bin3, fileSizeDist[3]); + for (int i = 4; i < ReconConstants.NUM_OF_FILE_SIZE_BINS; ++i) { + assertEquals(0, fileSizeDist[i]); + } + } + + @Test + public void testNormalizePathUptoBucket() { + // Test null or empty path + assertEquals("/", OmUtils.normalizePathUptoBucket(null)); + assertEquals("/", OmUtils.normalizePathUptoBucket("")); + + // Test path with leading slashes + assertEquals("volume1/bucket1/key1/key2", + OmUtils.normalizePathUptoBucket("///volume1/bucket1/key1/key2")); + + // Test volume and bucket names + assertEquals("volume1/bucket1", + OmUtils.normalizePathUptoBucket("volume1/bucket1")); + + // Test with additional segments + assertEquals("volume1/bucket1/key1/key2", + OmUtils.normalizePathUptoBucket("volume1/bucket1/key1/key2")); + + // Test path with multiple slashes in key names. + assertEquals("volume1/bucket1/key1//key2", + OmUtils.normalizePathUptoBucket("volume1/bucket1/key1//key2")); + + // Test path with volume, bucket, and special characters in keys + assertEquals("volume/bucket/key$%#1/./////////key$%#2", + OmUtils.normalizePathUptoBucket("volume/bucket/key$%#1/./////////key$%#2")); + } + + @Test + public void testConstructFullPath() throws IOException { + OmKeyInfo keyInfo = new OmKeyInfo.Builder() + .setKeyName(KEY_TWO) + .setVolumeName(VOL) + .setBucketName(BUCKET_ONE) + .setObjectID(KEY_TWO_OBJECT_ID) + .build(); + String fullPath = ReconUtils.constructFullPath(keyInfo, + reconNamespaceSummaryManager, reconOMMetadataManager); + String expectedPath = "vol/bucket1/" + KEY_TWO; + Assertions.assertEquals(expectedPath, fullPath); + + keyInfo = new OmKeyInfo.Builder() + .setKeyName(KEY_FIVE) + .setVolumeName(VOL) + .setBucketName(BUCKET_TWO) + .setObjectID(KEY_FIVE_OBJECT_ID) + .build(); + fullPath = ReconUtils.constructFullPath(keyInfo, + reconNamespaceSummaryManager, reconOMMetadataManager); + expectedPath = "vol/bucket2/" + KEY_FIVE; + Assertions.assertEquals(expectedPath, fullPath); + + keyInfo = new OmKeyInfo.Builder() + .setKeyName(KEY_EIGHT) + .setVolumeName(VOL_TWO) + .setBucketName(BUCKET_THREE) + .setObjectID(KEY_EIGHT_OBJECT_ID) + .build(); + fullPath = ReconUtils.constructFullPath(keyInfo, + reconNamespaceSummaryManager, reconOMMetadataManager); + expectedPath = "vol2/bucket3/" + KEY_EIGHT; + Assertions.assertEquals(expectedPath, fullPath); + + + keyInfo = new OmKeyInfo.Builder() + .setKeyName(KEY_ELEVEN) + .setVolumeName(VOL_TWO) + .setBucketName(BUCKET_FOUR) + .setObjectID(KEY_ELEVEN_OBJECT_ID) + .build(); + fullPath = ReconUtils.constructFullPath(keyInfo, + reconNamespaceSummaryManager, reconOMMetadataManager); + expectedPath = "vol2/bucket4/" + KEY_ELEVEN; + Assertions.assertEquals(expectedPath, fullPath); + } + + + /** + * Testing the following case. + * └── vol + * ├── bucket1 (OBS) + * │ ├── file1 + * │ ├── file2 + * │ └── file3 + * └── bucket2 (OBS) + * ├── file4 + * └── file5 + * └── vol2 + * ├── bucket3 (Legacy) + * │ ├── file8 + * │ ├── file9 + * │ └── file10 + * └── bucket4 (Legacy) + * └── file11 + * + * Write these keys to OM and + * replicate them. + * @throws Exception + */ + @SuppressWarnings("checkstyle:MethodLength") + private void populateOMDB() throws Exception { + + // write all keys + writeKeyToOm(reconOMMetadataManager, + KEY_ONE, + BUCKET_ONE, + VOL, + KEY_ONE, + KEY_ONE_OBJECT_ID, + BUCKET_ONE_OBJECT_ID, + BUCKET_ONE_OBJECT_ID, + VOL_OBJECT_ID, + FILE_ONE_SIZE, + getOBSBucketLayout()); + writeKeyToOm(reconOMMetadataManager, + KEY_TWO, + BUCKET_ONE, + VOL, + KEY_TWO, + KEY_TWO_OBJECT_ID, + BUCKET_ONE_OBJECT_ID, + BUCKET_ONE_OBJECT_ID, + VOL_OBJECT_ID, + FILE_TWO_SIZE, + getOBSBucketLayout()); + writeKeyToOm(reconOMMetadataManager, + KEY_THREE, + BUCKET_ONE, + VOL, + KEY_THREE, + KEY_THREE_OBJECT_ID, + BUCKET_ONE_OBJECT_ID, + BUCKET_ONE_OBJECT_ID, + VOL_OBJECT_ID, + FILE_THREE_SIZE, + getOBSBucketLayout()); + writeKeyToOm(reconOMMetadataManager, + KEY_FOUR, + BUCKET_TWO, + VOL, + KEY_FOUR, + KEY_FOUR_OBJECT_ID, + BUCKET_TWO_OBJECT_ID, + BUCKET_TWO_OBJECT_ID, + VOL_OBJECT_ID, + FILE_FOUR_SIZE, + getOBSBucketLayout()); + writeKeyToOm(reconOMMetadataManager, + KEY_FIVE, + BUCKET_TWO, + VOL, + KEY_FIVE, + KEY_FIVE_OBJECT_ID, + BUCKET_TWO_OBJECT_ID, + BUCKET_TWO_OBJECT_ID, + VOL_OBJECT_ID, + FILE_FIVE_SIZE, + getOBSBucketLayout()); + + writeKeyToOm(reconOMMetadataManager, + KEY_EIGHT, + BUCKET_THREE, + VOL_TWO, + KEY_EIGHT, + KEY_EIGHT_OBJECT_ID, + BUCKET_THREE_OBJECT_ID, + BUCKET_THREE_OBJECT_ID, + VOL_TWO_OBJECT_ID, + FILE_EIGHT_SIZE, + getLegacyBucketLayout()); + writeKeyToOm(reconOMMetadataManager, + KEY_NINE, + BUCKET_THREE, + VOL_TWO, + KEY_NINE, + KEY_NINE_OBJECT_ID, + BUCKET_THREE_OBJECT_ID, + BUCKET_THREE_OBJECT_ID, + VOL_TWO_OBJECT_ID, + FILE_NINE_SIZE, + getLegacyBucketLayout()); + writeKeyToOm(reconOMMetadataManager, + KEY_TEN, + BUCKET_THREE, + VOL_TWO, + KEY_TEN, + KEY_TEN_OBJECT_ID, + BUCKET_THREE_OBJECT_ID, + BUCKET_THREE_OBJECT_ID, + VOL_TWO_OBJECT_ID, + FILE_TEN_SIZE, + getLegacyBucketLayout()); + writeKeyToOm(reconOMMetadataManager, + KEY_ELEVEN, + BUCKET_FOUR, + VOL_TWO, + KEY_ELEVEN, + KEY_ELEVEN_OBJECT_ID, + PARENT_OBJECT_ID_ZERO, + BUCKET_FOUR_OBJECT_ID, + VOL_TWO_OBJECT_ID, + FILE_ELEVEN_SIZE, + getLegacyBucketLayout()); + } + + /** + * Create a new OM Metadata manager instance with one user, one vol, and two + * buckets. + * + * @throws IOException ioEx + */ + private static OMMetadataManager initializeNewOmMetadataManager( + File omDbDir, OzoneConfiguration omConfiguration) + throws IOException { + omConfiguration.set(OZONE_OM_DB_DIRS, + omDbDir.getAbsolutePath()); + omConfiguration.set(OMConfigKeys + .OZONE_OM_ENABLE_FILESYSTEM_PATHS, "false"); + OMMetadataManager omMetadataManager = new OmMetadataManagerImpl( + omConfiguration, null); + + String volumeKey = omMetadataManager.getVolumeKey(VOL); + OmVolumeArgs args = + OmVolumeArgs.newBuilder() + .setObjectID(VOL_OBJECT_ID) + .setVolume(VOL) + .setAdminName(TEST_USER) + .setOwnerName(TEST_USER) + .setQuotaInBytes(VOL_QUOTA) + .build(); + + String volume2Key = omMetadataManager.getVolumeKey(VOL_TWO); + OmVolumeArgs args2 = + OmVolumeArgs.newBuilder() + .setObjectID(VOL_TWO_OBJECT_ID) + .setVolume(VOL_TWO) + .setAdminName(TEST_USER) + .setOwnerName(TEST_USER) + .setQuotaInBytes(VOL_TWO_QUOTA) + .build(); + + omMetadataManager.getVolumeTable().put(volumeKey, args); + omMetadataManager.getVolumeTable().put(volume2Key, args2); + + OmBucketInfo bucketInfo = OmBucketInfo.newBuilder() + .setVolumeName(VOL) + .setBucketName(BUCKET_ONE) + .setObjectID(BUCKET_ONE_OBJECT_ID) + .setQuotaInBytes(BUCKET_ONE_QUOTA) + .setBucketLayout(getOBSBucketLayout()) + .build(); + + OmBucketInfo bucketInfo2 = OmBucketInfo.newBuilder() + .setVolumeName(VOL) + .setBucketName(BUCKET_TWO) + .setObjectID(BUCKET_TWO_OBJECT_ID) + .setQuotaInBytes(BUCKET_TWO_QUOTA) + .setBucketLayout(getOBSBucketLayout()) + .build(); + + OmBucketInfo bucketInfo3 = OmBucketInfo.newBuilder() + .setVolumeName(VOL_TWO) + .setBucketName(BUCKET_THREE) + .setObjectID(BUCKET_THREE_OBJECT_ID) + .setQuotaInBytes(BUCKET_THREE_QUOTA) + .setBucketLayout(getLegacyBucketLayout()) + .build(); + + OmBucketInfo bucketInfo4 = OmBucketInfo.newBuilder() + .setVolumeName(VOL_TWO) + .setBucketName(BUCKET_FOUR) + .setObjectID(BUCKET_FOUR_OBJECT_ID) + .setQuotaInBytes(BUCKET_FOUR_QUOTA) + .setBucketLayout(getLegacyBucketLayout()) + .build(); + + String bucketKey = omMetadataManager.getBucketKey( + bucketInfo.getVolumeName(), bucketInfo.getBucketName()); + String bucketKey2 = omMetadataManager.getBucketKey( + bucketInfo2.getVolumeName(), bucketInfo2.getBucketName()); + String bucketKey3 = omMetadataManager.getBucketKey( + bucketInfo3.getVolumeName(), bucketInfo3.getBucketName()); + String bucketKey4 = omMetadataManager.getBucketKey( + bucketInfo4.getVolumeName(), bucketInfo4.getBucketName()); + + omMetadataManager.getBucketTable().put(bucketKey, bucketInfo); + omMetadataManager.getBucketTable().put(bucketKey2, bucketInfo2); + omMetadataManager.getBucketTable().put(bucketKey3, bucketInfo3); + omMetadataManager.getBucketTable().put(bucketKey4, bucketInfo4); + + return omMetadataManager; + } + + private void setUpMultiBlockKey() throws IOException { + OmKeyLocationInfoGroup locationInfoGroup = + getLocationInfoGroup1(); + + // add the multi-block key to Recon's OM + writeKeyToOm(reconOMMetadataManager, + MULTI_BLOCK_FILE, + BUCKET_ONE, + VOL, + MULTI_BLOCK_FILE, + MULTI_BLOCK_KEY_OBJECT_ID, + PARENT_OBJECT_ID_ZERO, + BUCKET_ONE_OBJECT_ID, + VOL_OBJECT_ID, + Collections.singletonList(locationInfoGroup), + getOBSBucketLayout(), + FILE_THREE_SIZE); + } + + private OmKeyLocationInfoGroup getLocationInfoGroup1() { + List locationInfoList = new ArrayList<>(); + BlockID block1 = new BlockID(CONTAINER_ONE_ID, 0L); + BlockID block2 = new BlockID(CONTAINER_TWO_ID, 0L); + BlockID block3 = new BlockID(CONTAINER_THREE_ID, 0L); + + OmKeyLocationInfo location1 = new OmKeyLocationInfo.Builder() + .setBlockID(block1) + .setLength(BLOCK_ONE_LENGTH) + .build(); + OmKeyLocationInfo location2 = new OmKeyLocationInfo.Builder() + .setBlockID(block2) + .setLength(BLOCK_TWO_LENGTH) + .build(); + OmKeyLocationInfo location3 = new OmKeyLocationInfo.Builder() + .setBlockID(block3) + .setLength(BLOCK_THREE_LENGTH) + .build(); + locationInfoList.add(location1); + locationInfoList.add(location2); + locationInfoList.add(location3); + + return new OmKeyLocationInfoGroup(0L, locationInfoList); + } + + + private OmKeyLocationInfoGroup getLocationInfoGroup2() { + List locationInfoList = new ArrayList<>(); + BlockID block4 = new BlockID(CONTAINER_FOUR_ID, 0L); + BlockID block5 = new BlockID(CONTAINER_FIVE_ID, 0L); + BlockID block6 = new BlockID(CONTAINER_SIX_ID, 0L); + + OmKeyLocationInfo location4 = new OmKeyLocationInfo.Builder() + .setBlockID(block4) + .setLength(BLOCK_FOUR_LENGTH) + .build(); + OmKeyLocationInfo location5 = new OmKeyLocationInfo.Builder() + .setBlockID(block5) + .setLength(BLOCK_FIVE_LENGTH) + .build(); + OmKeyLocationInfo location6 = new OmKeyLocationInfo.Builder() + .setBlockID(block6) + .setLength(BLOCK_SIX_LENGTH) + .build(); + locationInfoList.add(location4); + locationInfoList.add(location5); + locationInfoList.add(location6); + return new OmKeyLocationInfoGroup(0L, locationInfoList); + + } + + @SuppressWarnings("checkstyle:MethodLength") + private void setUpMultiBlockReplicatedKeys() throws IOException { + OmKeyLocationInfoGroup locationInfoGroup1 = + getLocationInfoGroup1(); + OmKeyLocationInfoGroup locationInfoGroup2 = + getLocationInfoGroup2(); + + //vol/bucket1/file1 + writeKeyToOm(reconOMMetadataManager, + KEY_ONE, + BUCKET_ONE, + VOL, + KEY_ONE, + KEY_ONE_OBJECT_ID, + BUCKET_ONE_OBJECT_ID, + BUCKET_ONE_OBJECT_ID, + VOL_OBJECT_ID, + Collections.singletonList(locationInfoGroup1), + getOBSBucketLayout(), + FILE_ONE_SIZE); + + //vol/bucket1/file2 + writeKeyToOm(reconOMMetadataManager, + KEY_TWO, + BUCKET_ONE, + VOL, + KEY_TWO, + KEY_TWO_OBJECT_ID, + BUCKET_ONE_OBJECT_ID, + BUCKET_ONE_OBJECT_ID, + VOL_OBJECT_ID, + Collections.singletonList(locationInfoGroup2), + getOBSBucketLayout(), + FILE_TWO_SIZE); + + //vol/bucket1/file3 + writeKeyToOm(reconOMMetadataManager, + KEY_THREE, + BUCKET_ONE, + VOL, + KEY_THREE, + KEY_THREE_OBJECT_ID, + BUCKET_ONE_OBJECT_ID, + BUCKET_ONE_OBJECT_ID, + VOL_OBJECT_ID, + Collections.singletonList(locationInfoGroup1), + getOBSBucketLayout(), + FILE_THREE_SIZE); + + //vol/bucket2/file4 + writeKeyToOm(reconOMMetadataManager, + KEY_FOUR, + BUCKET_TWO, + VOL, + KEY_FOUR, + KEY_FOUR_OBJECT_ID, + BUCKET_TWO_OBJECT_ID, + BUCKET_TWO_OBJECT_ID, + VOL_OBJECT_ID, + Collections.singletonList(locationInfoGroup2), + getOBSBucketLayout(), + FILE_FOUR_SIZE); + + //vol/bucket2/file5 + writeKeyToOm(reconOMMetadataManager, + KEY_FIVE, + BUCKET_TWO, + VOL, + KEY_FIVE, + KEY_FIVE_OBJECT_ID, + BUCKET_TWO_OBJECT_ID, + BUCKET_TWO_OBJECT_ID, + VOL_OBJECT_ID, + Collections.singletonList(locationInfoGroup1), + getOBSBucketLayout(), + FILE_FIVE_SIZE); + + //vol2/bucket3/file8 + writeKeyToOm(reconOMMetadataManager, + KEY_EIGHT, + BUCKET_THREE, + VOL_TWO, + KEY_EIGHT, + KEY_EIGHT_OBJECT_ID, + BUCKET_THREE_OBJECT_ID, + BUCKET_THREE_OBJECT_ID, + VOL_TWO_OBJECT_ID, + Collections.singletonList(locationInfoGroup2), + getLegacyBucketLayout(), + FILE_EIGHT_SIZE); + + //vol2/bucket3/file9 + writeKeyToOm(reconOMMetadataManager, + KEY_NINE, + BUCKET_THREE, + VOL_TWO, + KEY_NINE, + KEY_NINE_OBJECT_ID, + BUCKET_THREE_OBJECT_ID, + BUCKET_THREE_OBJECT_ID, + VOL_TWO_OBJECT_ID, + Collections.singletonList(locationInfoGroup1), + getLegacyBucketLayout(), + FILE_NINE_SIZE); + + //vol2/bucket3/file10 + writeKeyToOm(reconOMMetadataManager, + KEY_TEN, + BUCKET_THREE, + VOL_TWO, + KEY_TEN, + KEY_TEN_OBJECT_ID, + BUCKET_THREE_OBJECT_ID, + BUCKET_THREE_OBJECT_ID, + VOL_TWO_OBJECT_ID, + Collections.singletonList(locationInfoGroup2), + getLegacyBucketLayout(), + FILE_TEN_SIZE); + + //vol2/bucket4/file11 + writeKeyToOm(reconOMMetadataManager, + KEY_ELEVEN, + BUCKET_FOUR, + VOL_TWO, + KEY_ELEVEN, + KEY_ELEVEN_OBJECT_ID, + BUCKET_FOUR_OBJECT_ID, + BUCKET_FOUR_OBJECT_ID, + VOL_TWO_OBJECT_ID, + Collections.singletonList(locationInfoGroup1), + getLegacyBucketLayout(), + FILE_ELEVEN_SIZE); + } + + /** + * Generate a set of mock container replica with a size of + * replication factor for container. + * + * @param replicationFactor number of replica + * @param containerID the container replicated based upon + * @return a set of container replica for testing + */ + private static Set generateMockContainerReplicas( + int replicationFactor, ContainerID containerID) { + Set result = new HashSet<>(); + for (int i = 0; i < replicationFactor; ++i) { + DatanodeDetails randomDatanode = randomDatanodeDetails(); + ContainerReplica replica = new ContainerReplica.ContainerReplicaBuilder() + .setContainerID(containerID) + .setContainerState( + StorageContainerDatanodeProtocolProtos.ContainerReplicaProto.State.OPEN) + .setDatanodeDetails(randomDatanode) + .build(); + result.add(replica); + } + return result; + } + + private static ReconStorageContainerManagerFacade getMockReconSCM() + throws ContainerNotFoundException { + ReconStorageContainerManagerFacade reconSCM = + mock(ReconStorageContainerManagerFacade.class); + ContainerManager containerManager = mock(ContainerManager.class); + + // Container 1 is 3-way replicated + ContainerID containerID1 = new ContainerID(CONTAINER_ONE_ID); + Set containerReplicas1 = generateMockContainerReplicas( + CONTAINER_ONE_REPLICA_COUNT, containerID1); + when(containerManager.getContainerReplicas(containerID1)) + .thenReturn(containerReplicas1); + + // Container 2 is under replicated with 2 replica + ContainerID containerID2 = new ContainerID(CONTAINER_TWO_ID); + Set containerReplicas2 = generateMockContainerReplicas( + CONTAINER_TWO_REPLICA_COUNT, containerID2); + when(containerManager.getContainerReplicas(containerID2)) + .thenReturn(containerReplicas2); + + // Container 3 is over replicated with 4 replica + ContainerID containerID3 = new ContainerID(CONTAINER_THREE_ID); + Set containerReplicas3 = generateMockContainerReplicas( + CONTAINER_THREE_REPLICA_COUNT, containerID3); + when(containerManager.getContainerReplicas(containerID3)) + .thenReturn(containerReplicas3); + + // Container 4 is replicated with 5 replica + ContainerID containerID4 = new ContainerID(CONTAINER_FOUR_ID); + Set containerReplicas4 = generateMockContainerReplicas( + CONTAINER_FOUR_REPLICA_COUNT, containerID4); + when(containerManager.getContainerReplicas(containerID4)) + .thenReturn(containerReplicas4); + + // Container 5 is replicated with 2 replica + ContainerID containerID5 = new ContainerID(CONTAINER_FIVE_ID); + Set containerReplicas5 = generateMockContainerReplicas( + CONTAINER_FIVE_REPLICA_COUNT, containerID5); + when(containerManager.getContainerReplicas(containerID5)) + .thenReturn(containerReplicas5); + + // Container 6 is replicated with 3 replica + ContainerID containerID6 = new ContainerID(CONTAINER_SIX_ID); + Set containerReplicas6 = generateMockContainerReplicas( + CONTAINER_SIX_REPLICA_COUNT, containerID6); + when(containerManager.getContainerReplicas(containerID6)) + .thenReturn(containerReplicas6); + + when(reconSCM.getContainerManager()).thenReturn(containerManager); + ReconNodeManager mockReconNodeManager = mock(ReconNodeManager.class); + when(mockReconNodeManager.getStats()).thenReturn(getMockSCMRootStat()); + when(reconSCM.getScmNodeManager()).thenReturn(mockReconNodeManager); + return reconSCM; + } + + private static BucketLayout getOBSBucketLayout() { + return BucketLayout.OBJECT_STORE; + } + + private static BucketLayout getLegacyBucketLayout() { + return BucketLayout.LEGACY; + } + + private static SCMNodeStat getMockSCMRootStat() { + return new SCMNodeStat(ROOT_QUOTA, ROOT_DATA_SIZE, + ROOT_QUOTA - ROOT_DATA_SIZE, 0L, 0L); + } + +} diff --git a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/scm/TestReconIncrementalContainerReportHandler.java b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/scm/TestReconIncrementalContainerReportHandler.java index cb11d7060d78..f50acc09258f 100644 --- a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/scm/TestReconIncrementalContainerReportHandler.java +++ b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/scm/TestReconIncrementalContainerReportHandler.java @@ -30,7 +30,9 @@ import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.UUID; import java.util.concurrent.TimeoutException; @@ -66,18 +68,28 @@ public class TestReconIncrementalContainerReportHandler private HDDSLayoutVersionManager versionManager; @Test - public void testProcessICR() throws IOException, NodeNotFoundException { + public void testProcessICR() + throws IOException, NodeNotFoundException, TimeoutException { ContainerID containerID = ContainerID.valueOf(100L); DatanodeDetails datanodeDetails = randomDatanodeDetails(); IncrementalContainerReportFromDatanode reportMock = mock(IncrementalContainerReportFromDatanode.class); when(reportMock.getDatanodeDetails()).thenReturn(datanodeDetails); + + ContainerWithPipeline containerWithPipeline = getTestContainer( + containerID.getId(), OPEN); + List containerWithPipelineList = new ArrayList<>(); + containerWithPipelineList.add(containerWithPipeline); + ReconContainerManager containerManager = getContainerManager(); IncrementalContainerReportProto containerReport = getIncrementalContainerReportProto(containerID, State.OPEN, datanodeDetails.getUuidString()); when(reportMock.getReport()).thenReturn(containerReport); + when(getContainerManager().getScmClient() + .getExistContainerWithPipelinesInBatch(any( + ArrayList.class))).thenReturn(containerWithPipelineList); final String path = GenericTestUtils.getTempPath(UUID.randomUUID().toString()); @@ -99,7 +111,6 @@ public void testProcessICR() throws IOException, NodeNotFoundException { nodeManager.register(datanodeDetails, null, null); - ReconContainerManager containerManager = getContainerManager(); ReconIncrementalContainerReportHandler reconIcr = new ReconIncrementalContainerReportHandler(nodeManager, containerManager, SCMContext.emptyContext()); diff --git a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/spi/impl/TestReconNamespaceSummaryManagerImpl.java b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/spi/impl/TestReconNamespaceSummaryManagerImpl.java index 46d7b0dfcc98..7460970995c1 100644 --- a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/spi/impl/TestReconNamespaceSummaryManagerImpl.java +++ b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/spi/impl/TestReconNamespaceSummaryManagerImpl.java @@ -113,9 +113,9 @@ public void testInitNSSummaryTable() throws IOException { private void putThreeNSMetadata() throws IOException { HashMap hmap = new HashMap<>(); - hmap.put(1L, new NSSummary(1, 2, testBucket, TEST_CHILD_DIR, "dir1")); - hmap.put(2L, new NSSummary(3, 4, testBucket, TEST_CHILD_DIR, "dir2")); - hmap.put(3L, new NSSummary(5, 6, testBucket, TEST_CHILD_DIR, "dir3")); + hmap.put(1L, new NSSummary(1, 2, testBucket, TEST_CHILD_DIR, "dir1", -1)); + hmap.put(2L, new NSSummary(3, 4, testBucket, TEST_CHILD_DIR, "dir2", -1)); + hmap.put(3L, new NSSummary(5, 6, testBucket, TEST_CHILD_DIR, "dir3", -1)); RDBBatchOperation rdbBatchOperation = new RDBBatchOperation(); for (Map.Entry entry: hmap.entrySet()) { reconNamespaceSummaryManager.batchStoreNSSummaries(rdbBatchOperation, diff --git a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/tasks/TestNSSummaryTask.java b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/tasks/TestNSSummaryTask.java index 6992c3100fb9..485804240d52 100644 --- a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/tasks/TestNSSummaryTask.java +++ b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/tasks/TestNSSummaryTask.java @@ -166,7 +166,7 @@ public void setUp() throws Exception { reconNamespaceSummaryManager.getNSSummary(BUCKET_THREE_OBJECT_ID); assertNotNull(nsSummaryForBucket1); assertNotNull(nsSummaryForBucket2); - assertNull(nsSummaryForBucket3); + assertNotNull(nsSummaryForBucket3); } @Test @@ -233,7 +233,7 @@ public void setUp() throws IOException { assertNotNull(nsSummaryForBucket2); nsSummaryForBucket3 = reconNamespaceSummaryManager.getNSSummary(BUCKET_THREE_OBJECT_ID); - assertNull(nsSummaryForBucket3); + assertNotNull(nsSummaryForBucket3); } private OMUpdateEventBatch processEventBatch() throws IOException { diff --git a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/tasks/TestNSSummaryTaskWithFSO.java b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/tasks/TestNSSummaryTaskWithFSO.java index 66c522cb4d70..ba2e7497417e 100644 --- a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/tasks/TestNSSummaryTaskWithFSO.java +++ b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/tasks/TestNSSummaryTaskWithFSO.java @@ -52,8 +52,8 @@ import static org.apache.hadoop.ozone.recon.OMMetadataManagerTestUtils.writeKeyToOm; import static org.apache.hadoop.ozone.recon.ReconServerConfigKeys.OZONE_RECON_NSSUMMARY_FLUSH_TO_DB_MAX_THRESHOLD; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; /** * Test for NSSummaryTaskWithFSO. @@ -270,6 +270,37 @@ public void testReprocessDirsUnderDir() throws Exception { assertEquals(DIR_ONE, nsSummaryInDir1.getDirName()); assertEquals(DIR_TWO, nsSummaryInDir2.getDirName()); } + + @Test + public void testDirectoryParentIdAssignment() throws Exception { + // Trigger reprocess to simulate reading from OM DB and processing into NSSummary. + nSSummaryTaskWithFso.reprocessWithFSO(reconOMMetadataManager); + + // Fetch NSSummary for DIR_ONE and verify its parent ID matches BUCKET_ONE_OBJECT_ID. + NSSummary nsSummaryDirOne = + reconNamespaceSummaryManager.getNSSummary(DIR_ONE_OBJECT_ID); + assertNotNull(nsSummaryDirOne, + "NSSummary for DIR_ONE should not be null."); + assertEquals(BUCKET_ONE_OBJECT_ID, nsSummaryDirOne.getParentId(), + "DIR_ONE's parent ID should match BUCKET_ONE_OBJECT_ID."); + + // Fetch NSSummary for DIR_TWO and verify its parent ID matches DIR_ONE_OBJECT_ID. + NSSummary nsSummaryDirTwo = + reconNamespaceSummaryManager.getNSSummary(DIR_TWO_OBJECT_ID); + assertNotNull(nsSummaryDirTwo, + "NSSummary for DIR_TWO should not be null."); + assertEquals(DIR_ONE_OBJECT_ID, nsSummaryDirTwo.getParentId(), + "DIR_TWO's parent ID should match DIR_ONE_OBJECT_ID."); + + // Fetch NSSummary for DIR_THREE and verify its parent ID matches DIR_ONE_OBJECT_ID. + NSSummary nsSummaryDirThree = + reconNamespaceSummaryManager.getNSSummary(DIR_THREE_OBJECT_ID); + assertNotNull(nsSummaryDirThree, + "NSSummary for DIR_THREE should not be null."); + assertEquals(DIR_ONE_OBJECT_ID, nsSummaryDirThree.getParentId(), + "DIR_THREE's parent ID should match DIR_ONE_OBJECT_ID."); + } + } /** @@ -462,6 +493,27 @@ public void testProcessDirDeleteRename() throws IOException { // after renaming dir1, check its new name assertEquals(DIR_ONE_RENAME, nsSummaryForDir1.getDirName()); } + + @Test + public void testParentIdAfterProcessEventBatch() throws IOException { + + // Verify the parent ID of DIR_FOUR after it's added under BUCKET_ONE. + NSSummary nsSummaryDirFour = + reconNamespaceSummaryManager.getNSSummary(DIR_FOUR_OBJECT_ID); + assertNotNull(nsSummaryDirFour, + "NSSummary for DIR_FOUR should not be null."); + assertEquals(BUCKET_ONE_OBJECT_ID, nsSummaryDirFour.getParentId(), + "DIR_FOUR's parent ID should match BUCKET_ONE_OBJECT_ID."); + + // Verify the parent ID of DIR_FIVE after it's added under BUCKET_TWO. + NSSummary nsSummaryDirFive = + reconNamespaceSummaryManager.getNSSummary(DIR_FIVE_OBJECT_ID); + assertNotNull(nsSummaryDirFive, + "NSSummary for DIR_FIVE should not be null."); + assertEquals(BUCKET_TWO_OBJECT_ID, nsSummaryDirFive.getParentId(), + "DIR_FIVE's parent ID should match BUCKET_TWO_OBJECT_ID."); + } + } /** diff --git a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/tasks/TestNSSummaryTaskWithLegacyOBSLayout.java b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/tasks/TestNSSummaryTaskWithLegacyOBSLayout.java new file mode 100644 index 000000000000..db4803676390 --- /dev/null +++ b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/tasks/TestNSSummaryTaskWithLegacyOBSLayout.java @@ -0,0 +1,554 @@ +/* + * 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.ozone.recon.tasks; + +import org.apache.hadoop.hdds.client.StandaloneReplicationConfig; +import org.apache.hadoop.hdds.conf.OzoneConfiguration; +import org.apache.hadoop.hdds.protocol.proto.HddsProtos; +import org.apache.hadoop.hdds.utils.db.RDBBatchOperation; +import org.apache.hadoop.ozone.om.OMConfigKeys; +import org.apache.hadoop.ozone.om.OMMetadataManager; +import org.apache.hadoop.ozone.om.OmMetadataManagerImpl; +import org.apache.hadoop.ozone.om.helpers.BucketLayout; +import org.apache.hadoop.ozone.om.helpers.OmBucketInfo; +import org.apache.hadoop.ozone.om.helpers.OmKeyInfo; +import org.apache.hadoop.ozone.om.helpers.OmVolumeArgs; +import org.apache.hadoop.ozone.recon.ReconConstants; +import org.apache.hadoop.ozone.recon.ReconTestInjector; +import org.apache.hadoop.ozone.recon.api.types.NSSummary; +import org.apache.hadoop.ozone.recon.recovery.ReconOMMetadataManager; +import org.apache.hadoop.ozone.recon.spi.ReconNamespaceSummaryManager; +import org.apache.hadoop.ozone.recon.spi.impl.OzoneManagerServiceProviderImpl; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.Set; + +import static org.apache.hadoop.ozone.OzoneConsts.OM_KEY_PREFIX; +import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_DB_DIRS; +import static org.apache.hadoop.ozone.recon.OMMetadataManagerTestUtils.writeKeyToOm; +import static org.apache.hadoop.ozone.recon.OMMetadataManagerTestUtils.getMockOzoneManagerServiceProviderWithFSO; +import static org.apache.hadoop.ozone.recon.OMMetadataManagerTestUtils.getTestReconOmMetadataManager; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * Test for NSSummaryTaskWithLegacy focusing on the OBS (Object Store) layout. + */ +public final class TestNSSummaryTaskWithLegacyOBSLayout { + + private static ReconNamespaceSummaryManager reconNamespaceSummaryManager; + private static ReconOMMetadataManager reconOMMetadataManager; + private static OzoneConfiguration ozoneConfiguration; + private static NSSummaryTaskWithLegacy nSSummaryTaskWithLegacy; + + private static OMMetadataManager omMetadataManager; + private static OzoneConfiguration omConfiguration; + + // Object names + private static final String VOL = "vol"; + private static final String BUCKET_ONE = "bucket1"; + private static final String BUCKET_TWO = "bucket2"; + private static final String KEY_ONE = "key1"; + private static final String KEY_TWO = "key2"; + private static final String KEY_THREE = "dir1/dir2/key3"; + private static final String KEY_FOUR = "key4///////////"; + private static final String KEY_FIVE = "//////////"; + private static final String KEY_SIX = "key6"; + private static final String KEY_SEVEN = "/////key7"; + + private static final String TEST_USER = "TestUser"; + + private static final long PARENT_OBJECT_ID_ZERO = 0L; + private static final long VOL_OBJECT_ID = 0L; + private static final long BUCKET_ONE_OBJECT_ID = 1L; + private static final long BUCKET_TWO_OBJECT_ID = 2L; + private static final long KEY_ONE_OBJECT_ID = 3L; + private static final long KEY_TWO_OBJECT_ID = 5L; + private static final long KEY_FOUR_OBJECT_ID = 6L; + private static final long KEY_THREE_OBJECT_ID = 8L; + private static final long KEY_FIVE_OBJECT_ID = 9L; + private static final long KEY_SIX_OBJECT_ID = 10L; + private static final long KEY_SEVEN_OBJECT_ID = 11L; + + + private static final long KEY_ONE_SIZE = 500L; + private static final long KEY_TWO_OLD_SIZE = 1025L; + private static final long KEY_TWO_UPDATE_SIZE = 1023L; + private static final long KEY_THREE_SIZE = + ReconConstants.MAX_FILE_SIZE_UPPER_BOUND - 100L; + private static final long KEY_FOUR_SIZE = 2050L; + private static final long KEY_FIVE_SIZE = 100L; + private static final long KEY_SIX_SIZE = 6000L; + private static final long KEY_SEVEN_SIZE = 7000L; + + private TestNSSummaryTaskWithLegacyOBSLayout() { + } + + @BeforeAll + public static void setUp(@TempDir File tmpDir) throws Exception { + initializeNewOmMetadataManager(new File(tmpDir, "om")); + OzoneManagerServiceProviderImpl ozoneManagerServiceProvider = + getMockOzoneManagerServiceProviderWithFSO(); + reconOMMetadataManager = getTestReconOmMetadataManager(omMetadataManager, + new File(tmpDir, "recon")); + ozoneConfiguration = new OzoneConfiguration(); + ozoneConfiguration.setBoolean(OMConfigKeys.OZONE_OM_ENABLE_FILESYSTEM_PATHS, + false); + + ReconTestInjector reconTestInjector = + new ReconTestInjector.Builder(tmpDir) + .withReconOm(reconOMMetadataManager) + .withOmServiceProvider(ozoneManagerServiceProvider) + .withReconSqlDb() + .withContainerDB() + .build(); + reconNamespaceSummaryManager = + reconTestInjector.getInstance(ReconNamespaceSummaryManager.class); + + NSSummary nonExistentSummary = + reconNamespaceSummaryManager.getNSSummary(BUCKET_ONE_OBJECT_ID); + assertNull(nonExistentSummary); + + populateOMDB(); + + nSSummaryTaskWithLegacy = new NSSummaryTaskWithLegacy( + reconNamespaceSummaryManager, + reconOMMetadataManager, ozoneConfiguration); + } + + /** + * Nested class for testing NSSummaryTaskWithLegacy reprocess. + */ + @Nested + public class TestReprocess { + + private NSSummary nsSummaryForBucket1; + private NSSummary nsSummaryForBucket2; + + @BeforeEach + public void setUp() throws IOException { + // write a NSSummary prior to reprocess + // verify it got cleaned up after. + NSSummary staleNSSummary = new NSSummary(); + RDBBatchOperation rdbBatchOperation = new RDBBatchOperation(); + reconNamespaceSummaryManager.batchStoreNSSummaries(rdbBatchOperation, -1L, + staleNSSummary); + reconNamespaceSummaryManager.commitBatchOperation(rdbBatchOperation); + + // Verify commit + assertNotNull(reconNamespaceSummaryManager.getNSSummary(-1L)); + + // reinit Recon RocksDB's namespace CF. + reconNamespaceSummaryManager.clearNSSummaryTable(); + + nSSummaryTaskWithLegacy.reprocessWithLegacy(reconOMMetadataManager); + assertNull(reconNamespaceSummaryManager.getNSSummary(-1L)); + + nsSummaryForBucket1 = + reconNamespaceSummaryManager.getNSSummary(BUCKET_ONE_OBJECT_ID); + nsSummaryForBucket2 = + reconNamespaceSummaryManager.getNSSummary(BUCKET_TWO_OBJECT_ID); + assertNotNull(nsSummaryForBucket1); + assertNotNull(nsSummaryForBucket2); + } + + @Test + public void testReprocessNSSummaryNull() throws IOException { + assertNull(reconNamespaceSummaryManager.getNSSummary(-1L)); + } + + @Test + public void testReprocessGetFiles() { + assertEquals(3, nsSummaryForBucket1.getNumOfFiles()); + assertEquals(2, nsSummaryForBucket2.getNumOfFiles()); + + assertEquals(KEY_ONE_SIZE + KEY_TWO_OLD_SIZE + KEY_THREE_SIZE, + nsSummaryForBucket1.getSizeOfFiles()); + assertEquals(KEY_FOUR_SIZE + KEY_FIVE_SIZE, + nsSummaryForBucket2.getSizeOfFiles()); + } + + @Test + public void testReprocessFileBucketSize() { + int[] fileDistBucket1 = nsSummaryForBucket1.getFileSizeBucket(); + int[] fileDistBucket2 = nsSummaryForBucket2.getFileSizeBucket(); + assertEquals(ReconConstants.NUM_OF_FILE_SIZE_BINS, + fileDistBucket1.length); + assertEquals(ReconConstants.NUM_OF_FILE_SIZE_BINS, + fileDistBucket2.length); + + // Check for 1's and 0's in fileDistBucket1 + int[] expectedIndexes1 = {0, 1, 40}; + for (int index = 0; index < fileDistBucket1.length; index++) { + if (contains(expectedIndexes1, index)) { + assertEquals(1, fileDistBucket1[index]); + } else { + assertEquals(0, fileDistBucket1[index]); + } + } + + // Check for 1's and 0's in fileDistBucket2 + int[] expectedIndexes2 = {0, 2}; + for (int index = 0; index < fileDistBucket2.length; index++) { + if (contains(expectedIndexes2, index)) { + assertEquals(1, fileDistBucket2[index]); + } else { + assertEquals(0, fileDistBucket2[index]); + } + } + } + + } + + /** + * Nested class for testing NSSummaryTaskWithLegacy process. + */ + @Nested + public class TestProcess { + + private NSSummary nsSummaryForBucket1; + private NSSummary nsSummaryForBucket2; + + private OMDBUpdateEvent keyEvent1; + private OMDBUpdateEvent keyEvent2; + private OMDBUpdateEvent keyEvent3; + private OMDBUpdateEvent keyEvent4; + + @BeforeEach + public void setUp() throws IOException { + // reinit Recon RocksDB's namespace CF. + reconNamespaceSummaryManager.clearNSSummaryTable(); + nSSummaryTaskWithLegacy.reprocessWithLegacy(reconOMMetadataManager); + nSSummaryTaskWithLegacy.processWithLegacy(processEventBatch()); + + nsSummaryForBucket1 = + reconNamespaceSummaryManager.getNSSummary(BUCKET_ONE_OBJECT_ID); + assertNotNull(nsSummaryForBucket1); + nsSummaryForBucket2 = + reconNamespaceSummaryManager.getNSSummary(BUCKET_TWO_OBJECT_ID); + assertNotNull(nsSummaryForBucket2); + } + + private OMUpdateEventBatch processEventBatch() throws IOException { + // Test PUT Event. + // PUT Key6 in Bucket2. + String omPutKey = + OM_KEY_PREFIX + VOL + + OM_KEY_PREFIX + BUCKET_TWO + + OM_KEY_PREFIX + KEY_SIX; + OmKeyInfo omPutKeyInfo = buildOmKeyInfo(VOL, BUCKET_TWO, KEY_SIX, + KEY_SIX, KEY_SIX_OBJECT_ID, BUCKET_TWO_OBJECT_ID, KEY_SIX_SIZE); + keyEvent1 = new OMDBUpdateEvent. + OMUpdateEventBuilder() + .setKey(omPutKey) + .setValue(omPutKeyInfo) + .setTable(omMetadataManager.getKeyTable(getBucketLayout()) + .getName()) + .setAction(OMDBUpdateEvent.OMDBUpdateAction.PUT) + .build(); + // PUT Key7 in Bucket1. + omPutKey = + OM_KEY_PREFIX + VOL + + OM_KEY_PREFIX + BUCKET_ONE + + OM_KEY_PREFIX + KEY_SEVEN; + omPutKeyInfo = buildOmKeyInfo(VOL, BUCKET_ONE, KEY_SEVEN, + KEY_SEVEN, KEY_SEVEN_OBJECT_ID, BUCKET_ONE_OBJECT_ID, KEY_SEVEN_SIZE); + keyEvent2 = new OMDBUpdateEvent. + OMUpdateEventBuilder() + .setKey(omPutKey) + .setValue(omPutKeyInfo) + .setTable(omMetadataManager.getKeyTable(getBucketLayout()) + .getName()) + .setAction(OMDBUpdateEvent.OMDBUpdateAction.PUT) + .build(); + + // Test DELETE Event. + // Delete Key1 in Bucket1. + String omDeleteKey = + OM_KEY_PREFIX + VOL + + OM_KEY_PREFIX + BUCKET_ONE + + OM_KEY_PREFIX + KEY_ONE; + OmKeyInfo omDeleteKeyInfo = buildOmKeyInfo(VOL, BUCKET_ONE, KEY_ONE, + KEY_ONE, KEY_ONE_OBJECT_ID, BUCKET_ONE_OBJECT_ID, KEY_ONE_SIZE); + keyEvent3 = new OMDBUpdateEvent. + OMUpdateEventBuilder() + .setKey(omDeleteKey) + .setTable(omMetadataManager.getKeyTable(getBucketLayout()) + .getName()) + .setValue(omDeleteKeyInfo) + .setAction(OMDBUpdateEvent.OMDBUpdateAction.DELETE) + .build(); + + // Test UPDATE Event. + // Resize Key2 in Bucket1. + String omResizeKey = + OM_KEY_PREFIX + VOL + + OM_KEY_PREFIX + BUCKET_ONE + + OM_KEY_PREFIX + KEY_TWO; + OmKeyInfo oldOmResizeKeyInfo = + buildOmKeyInfo(VOL, BUCKET_ONE, KEY_TWO, KEY_TWO, KEY_TWO_OBJECT_ID, + BUCKET_ONE_OBJECT_ID, KEY_TWO_OLD_SIZE); + OmKeyInfo newOmResizeKeyInfo = + buildOmKeyInfo(VOL, BUCKET_ONE, KEY_TWO, KEY_TWO, KEY_TWO_OBJECT_ID, + BUCKET_ONE_OBJECT_ID, KEY_TWO_OLD_SIZE + 100); + keyEvent4 = new OMDBUpdateEvent. + OMUpdateEventBuilder() + .setKey(omResizeKey) + .setOldValue(oldOmResizeKeyInfo) + .setValue(newOmResizeKeyInfo) + .setTable(omMetadataManager.getKeyTable(getBucketLayout()) + .getName()) + .setAction(OMDBUpdateEvent.OMDBUpdateAction.UPDATE) + .build(); + + return new OMUpdateEventBatch( + Arrays.asList(keyEvent1, keyEvent2, keyEvent3, keyEvent4)); + } + + @Test + public void testProcessForCount() throws IOException { + assertNotNull(nsSummaryForBucket1); + assertEquals(3, nsSummaryForBucket1.getNumOfFiles()); + assertNotNull(nsSummaryForBucket2); + assertEquals(3, nsSummaryForBucket2.getNumOfFiles()); + + Set childDirBucket1 = nsSummaryForBucket1.getChildDir(); + assertEquals(0, childDirBucket1.size()); + Set childDirBucket2 = nsSummaryForBucket2.getChildDir(); + assertEquals(0, childDirBucket2.size()); + } + + @Test + public void testProcessForSize() throws IOException { + assertNotNull(nsSummaryForBucket1); + assertEquals( + KEY_THREE_SIZE + KEY_SEVEN_SIZE + KEY_TWO_OLD_SIZE + 100, + nsSummaryForBucket1.getSizeOfFiles()); + assertNotNull(nsSummaryForBucket2); + assertEquals(KEY_FOUR_SIZE + KEY_FIVE_SIZE + KEY_SIX_SIZE, + nsSummaryForBucket2.getSizeOfFiles()); + } + + + @Test + public void testProcessFileBucketSize() { + int[] fileDistBucket1 = nsSummaryForBucket1.getFileSizeBucket(); + int[] fileDistBucket2 = nsSummaryForBucket2.getFileSizeBucket(); + assertEquals(ReconConstants.NUM_OF_FILE_SIZE_BINS, + fileDistBucket1.length); + assertEquals(ReconConstants.NUM_OF_FILE_SIZE_BINS, + fileDistBucket2.length); + + // Check for 1's and 0's in fileDistBucket1 + int[] expectedIndexes1 = {1, 3, 40}; + for (int index = 0; index < fileDistBucket1.length; index++) { + if (contains(expectedIndexes1, index)) { + assertEquals(1, fileDistBucket1[index]); + } else { + assertEquals(0, fileDistBucket1[index]); + } + } + + // Check for 1's and 0's in fileDistBucket2 + int[] expectedIndexes2 = {0, 2, 3}; + for (int index = 0; index < fileDistBucket2.length; index++) { + if (contains(expectedIndexes2, index)) { + assertEquals(1, fileDistBucket2[index]); + } else { + assertEquals(0, fileDistBucket2[index]); + } + } + } + + } + + /** + * Populate OMDB with the following configs. + * vol + * / \ + * bucket1 bucket2 + * / \ \ \ \ + * key1 key2 key3 key4 key5 + * + * @throws IOException + */ + private static void populateOMDB() throws IOException { + writeKeyToOm(reconOMMetadataManager, + KEY_ONE, + BUCKET_ONE, + VOL, + KEY_ONE, + KEY_ONE_OBJECT_ID, + BUCKET_ONE_OBJECT_ID, + BUCKET_ONE_OBJECT_ID, + VOL_OBJECT_ID, + KEY_ONE_SIZE, + getBucketLayout()); + writeKeyToOm(reconOMMetadataManager, + KEY_TWO, + BUCKET_ONE, + VOL, + KEY_TWO, + KEY_TWO_OBJECT_ID, + BUCKET_ONE_OBJECT_ID, + BUCKET_ONE_OBJECT_ID, + VOL_OBJECT_ID, + KEY_TWO_OLD_SIZE, + getBucketLayout()); + writeKeyToOm(reconOMMetadataManager, + KEY_THREE, + BUCKET_ONE, + VOL, + KEY_THREE, + KEY_THREE_OBJECT_ID, + BUCKET_ONE_OBJECT_ID, + BUCKET_ONE_OBJECT_ID, + VOL_OBJECT_ID, + KEY_THREE_SIZE, + getBucketLayout()); + + writeKeyToOm(reconOMMetadataManager, + KEY_FOUR, + BUCKET_TWO, + VOL, + KEY_FOUR, + KEY_FOUR_OBJECT_ID, + BUCKET_TWO_OBJECT_ID, + BUCKET_TWO_OBJECT_ID, + VOL_OBJECT_ID, + KEY_FOUR_SIZE, + getBucketLayout()); + writeKeyToOm(reconOMMetadataManager, + KEY_FIVE, + BUCKET_TWO, + VOL, + KEY_FIVE, + KEY_FIVE_OBJECT_ID, + PARENT_OBJECT_ID_ZERO, + BUCKET_TWO_OBJECT_ID, + VOL_OBJECT_ID, + KEY_FIVE_SIZE, + getBucketLayout()); + } + + /** + * Create a new OM Metadata manager instance with one user, one vol, and two + * buckets. + * + * @throws IOException ioEx + */ + private static void initializeNewOmMetadataManager( + File omDbDir) + throws IOException { + omConfiguration = new OzoneConfiguration(); + omConfiguration.set(OZONE_OM_DB_DIRS, + omDbDir.getAbsolutePath()); + omConfiguration.set(OMConfigKeys + .OZONE_OM_ENABLE_FILESYSTEM_PATHS, "true"); + omMetadataManager = new OmMetadataManagerImpl( + omConfiguration, null); + + String volumeKey = omMetadataManager.getVolumeKey(VOL); + OmVolumeArgs args = + OmVolumeArgs.newBuilder() + .setObjectID(VOL_OBJECT_ID) + .setVolume(VOL) + .setAdminName(TEST_USER) + .setOwnerName(TEST_USER) + .build(); + omMetadataManager.getVolumeTable().put(volumeKey, args); + + OmBucketInfo bucketInfo1 = OmBucketInfo.newBuilder() + .setVolumeName(VOL) + .setBucketName(BUCKET_ONE) + .setObjectID(BUCKET_ONE_OBJECT_ID) + .setBucketLayout(getBucketLayout()) + .build(); + + OmBucketInfo bucketInfo2 = OmBucketInfo.newBuilder() + .setVolumeName(VOL) + .setBucketName(BUCKET_TWO) + .setObjectID(BUCKET_TWO_OBJECT_ID) + .setBucketLayout(getBucketLayout()) + .build(); + + String bucketKey = omMetadataManager.getBucketKey( + bucketInfo1.getVolumeName(), bucketInfo1.getBucketName()); + String bucketKey2 = omMetadataManager.getBucketKey( + bucketInfo2.getVolumeName(), bucketInfo2.getBucketName()); + + omMetadataManager.getBucketTable().put(bucketKey, bucketInfo1); + omMetadataManager.getBucketTable().put(bucketKey2, bucketInfo2); + } + + /** + * Build a key info for put/update action. + * + * @param volume volume name + * @param bucket bucket name + * @param key key name + * @param fileName file name + * @param objectID object ID + * @param parentObjectId parent object ID + * @param dataSize file size + * @return the KeyInfo + */ + private static OmKeyInfo buildOmKeyInfo(String volume, + String bucket, + String key, + String fileName, + long objectID, + long parentObjectId, + long dataSize) { + return new OmKeyInfo.Builder() + .setBucketName(bucket) + .setVolumeName(volume) + .setKeyName(key) + .setFileName(fileName) + .setReplicationConfig( + StandaloneReplicationConfig.getInstance( + HddsProtos.ReplicationFactor.ONE)) + .setObjectID(objectID) + .setParentObjectID(parentObjectId) + .setDataSize(dataSize) + .build(); + } + + // Helper method to check if an array contains a specific value + private boolean contains(int[] arr, int value) { + for (int num : arr) { + if (num == value) { + return true; + } + } + return false; + } + + private static BucketLayout getBucketLayout() { + return BucketLayout.LEGACY; + } +} diff --git a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/tasks/TestNSSummaryTaskWithOBS.java b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/tasks/TestNSSummaryTaskWithOBS.java new file mode 100644 index 000000000000..8f9d6b2990a5 --- /dev/null +++ b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/tasks/TestNSSummaryTaskWithOBS.java @@ -0,0 +1,548 @@ +/** + * 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.ozone.recon.tasks; + +import org.apache.hadoop.hdds.client.StandaloneReplicationConfig; +import org.apache.hadoop.hdds.conf.OzoneConfiguration; +import org.apache.hadoop.hdds.protocol.proto.HddsProtos; +import org.apache.hadoop.hdds.utils.db.RDBBatchOperation; +import org.apache.hadoop.ozone.om.OMConfigKeys; +import org.apache.hadoop.ozone.om.OMMetadataManager; +import org.apache.hadoop.ozone.om.OmMetadataManagerImpl; +import org.apache.hadoop.ozone.om.helpers.BucketLayout; +import org.apache.hadoop.ozone.om.helpers.OmBucketInfo; +import org.apache.hadoop.ozone.om.helpers.OmKeyInfo; +import org.apache.hadoop.ozone.om.helpers.OmVolumeArgs; +import org.apache.hadoop.ozone.recon.ReconConstants; +import org.apache.hadoop.ozone.recon.ReconTestInjector; +import org.apache.hadoop.ozone.recon.api.types.NSSummary; +import org.apache.hadoop.ozone.recon.recovery.ReconOMMetadataManager; +import org.apache.hadoop.ozone.recon.spi.ReconNamespaceSummaryManager; +import org.apache.hadoop.ozone.recon.spi.impl.OzoneManagerServiceProviderImpl; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.util.Arrays; +import java.util.Set; + +import static org.apache.hadoop.ozone.OzoneConsts.OM_KEY_PREFIX; +import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_DB_DIRS; +import static org.apache.hadoop.ozone.recon.OMMetadataManagerTestUtils.getTestReconOmMetadataManager; +import static org.apache.hadoop.ozone.recon.OMMetadataManagerTestUtils.writeKeyToOm; +import static org.apache.hadoop.ozone.recon.OMMetadataManagerTestUtils.getMockOzoneManagerServiceProviderWithFSO; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * Unit test for NSSummaryTaskWithOBS. + */ +public final class TestNSSummaryTaskWithOBS implements Serializable { + private static ReconNamespaceSummaryManager reconNamespaceSummaryManager; + private static OMMetadataManager omMetadataManager; + private static ReconOMMetadataManager reconOMMetadataManager; + private static NSSummaryTaskWithOBS nSSummaryTaskWithOBS; + private static OzoneConfiguration omConfiguration; + + // Object names + private static final String VOL = "vol"; + private static final String BUCKET_ONE = "bucket1"; + private static final String BUCKET_TWO = "bucket2"; + private static final String KEY_ONE = "key1"; + private static final String KEY_TWO = "key2"; + private static final String KEY_THREE = "dir1/dir2/key3"; + private static final String KEY_FOUR = "key4///////////"; + private static final String KEY_FIVE = "//////////"; + private static final String KEY_SIX = "key6"; + private static final String KEY_SEVEN = "key7"; + + private static final String TEST_USER = "TestUser"; + + private static final long PARENT_OBJECT_ID_ZERO = 0L; + private static final long VOL_OBJECT_ID = 0L; + private static final long BUCKET_ONE_OBJECT_ID = 1L; + private static final long BUCKET_TWO_OBJECT_ID = 2L; + private static final long KEY_ONE_OBJECT_ID = 3L; + private static final long KEY_TWO_OBJECT_ID = 5L; + private static final long KEY_FOUR_OBJECT_ID = 6L; + private static final long KEY_THREE_OBJECT_ID = 8L; + private static final long KEY_FIVE_OBJECT_ID = 9L; + private static final long KEY_SIX_OBJECT_ID = 10L; + private static final long KEY_SEVEN_OBJECT_ID = 11L; + + + private static final long KEY_ONE_SIZE = 500L; + private static final long KEY_TWO_OLD_SIZE = 1025L; + private static final long KEY_TWO_UPDATE_SIZE = 1023L; + private static final long KEY_THREE_SIZE = + ReconConstants.MAX_FILE_SIZE_UPPER_BOUND - 100L; + private static final long KEY_FOUR_SIZE = 2050L; + private static final long KEY_FIVE_SIZE = 100L; + private static final long KEY_SIX_SIZE = 6000L; + private static final long KEY_SEVEN_SIZE = 7000L; + + private TestNSSummaryTaskWithOBS() { + } + + @BeforeAll + public static void setUp(@TempDir File tmpDir) throws Exception { + initializeNewOmMetadataManager(new File(tmpDir, "om")); + OzoneManagerServiceProviderImpl ozoneManagerServiceProvider = + getMockOzoneManagerServiceProviderWithFSO(); + reconOMMetadataManager = getTestReconOmMetadataManager(omMetadataManager, + new File(tmpDir, "recon")); + + ReconTestInjector reconTestInjector = + new ReconTestInjector.Builder(tmpDir) + .withReconOm(reconOMMetadataManager) + .withOmServiceProvider(ozoneManagerServiceProvider) + .withReconSqlDb() + .withContainerDB() + .build(); + reconNamespaceSummaryManager = + reconTestInjector.getInstance(ReconNamespaceSummaryManager.class); + + NSSummary nonExistentSummary = + reconNamespaceSummaryManager.getNSSummary(BUCKET_ONE_OBJECT_ID); + assertNull(nonExistentSummary); + + populateOMDB(); + + nSSummaryTaskWithOBS = new NSSummaryTaskWithOBS( + reconNamespaceSummaryManager, + reconOMMetadataManager, omConfiguration); + } + + /** + * Nested class for testing NSSummaryTaskWithOBS reprocess. + */ + @Nested + public class TestReprocess { + + private NSSummary nsSummaryForBucket1; + private NSSummary nsSummaryForBucket2; + + @BeforeEach + public void setUp() throws IOException { + // write a NSSummary prior to reprocess + // verify it got cleaned up after. + NSSummary staleNSSummary = new NSSummary(); + RDBBatchOperation rdbBatchOperation = new RDBBatchOperation(); + reconNamespaceSummaryManager.batchStoreNSSummaries(rdbBatchOperation, -1L, + staleNSSummary); + reconNamespaceSummaryManager.commitBatchOperation(rdbBatchOperation); + + // Verify commit + assertNotNull(reconNamespaceSummaryManager.getNSSummary(-1L)); + + // reinit Recon RocksDB's namespace CF. + reconNamespaceSummaryManager.clearNSSummaryTable(); + + nSSummaryTaskWithOBS.reprocessWithOBS(reconOMMetadataManager); + assertNull(reconNamespaceSummaryManager.getNSSummary(-1L)); + + nsSummaryForBucket1 = + reconNamespaceSummaryManager.getNSSummary(BUCKET_ONE_OBJECT_ID); + nsSummaryForBucket2 = + reconNamespaceSummaryManager.getNSSummary(BUCKET_TWO_OBJECT_ID); + assertNotNull(nsSummaryForBucket1); + assertNotNull(nsSummaryForBucket2); + } + + @Test + public void testReprocessNSSummaryNull() throws IOException { + assertNull(reconNamespaceSummaryManager.getNSSummary(-1L)); + } + + @Test + public void testReprocessGetFiles() { + assertEquals(3, nsSummaryForBucket1.getNumOfFiles()); + assertEquals(2, nsSummaryForBucket2.getNumOfFiles()); + + assertEquals(KEY_ONE_SIZE + KEY_TWO_OLD_SIZE + KEY_THREE_SIZE, + nsSummaryForBucket1.getSizeOfFiles()); + assertEquals(KEY_FOUR_SIZE + KEY_FIVE_SIZE, + nsSummaryForBucket2.getSizeOfFiles()); + } + + @Test + public void testReprocessFileBucketSize() { + int[] fileDistBucket1 = nsSummaryForBucket1.getFileSizeBucket(); + int[] fileDistBucket2 = nsSummaryForBucket2.getFileSizeBucket(); + assertEquals(ReconConstants.NUM_OF_FILE_SIZE_BINS, + fileDistBucket1.length); + assertEquals(ReconConstants.NUM_OF_FILE_SIZE_BINS, + fileDistBucket2.length); + + // Check for 1's and 0's in fileDistBucket1 + int[] expectedIndexes1 = {0, 1, 40}; + for (int index = 0; index < fileDistBucket1.length; index++) { + if (contains(expectedIndexes1, index)) { + assertEquals(1, fileDistBucket1[index]); + } else { + assertEquals(0, fileDistBucket1[index]); + } + } + + // Check for 1's and 0's in fileDistBucket2 + int[] expectedIndexes2 = {0, 2}; + for (int index = 0; index < fileDistBucket2.length; index++) { + if (contains(expectedIndexes2, index)) { + assertEquals(1, fileDistBucket2[index]); + } else { + assertEquals(0, fileDistBucket2[index]); + } + } + } + + } + + /** + * Nested class for testing NSSummaryTaskWithOBS process. + */ + @Nested + public class TestProcess { + + private NSSummary nsSummaryForBucket1; + private NSSummary nsSummaryForBucket2; + + private OMDBUpdateEvent keyEvent1; + private OMDBUpdateEvent keyEvent2; + private OMDBUpdateEvent keyEvent3; + private OMDBUpdateEvent keyEvent4; + + @BeforeEach + public void setUp() throws IOException { + // reinit Recon RocksDB's namespace CF. + reconNamespaceSummaryManager.clearNSSummaryTable(); + nSSummaryTaskWithOBS.reprocessWithOBS(reconOMMetadataManager); + nSSummaryTaskWithOBS.processWithOBS(processEventBatch()); + + nsSummaryForBucket1 = + reconNamespaceSummaryManager.getNSSummary(BUCKET_ONE_OBJECT_ID); + assertNotNull(nsSummaryForBucket1); + nsSummaryForBucket2 = + reconNamespaceSummaryManager.getNSSummary(BUCKET_TWO_OBJECT_ID); + assertNotNull(nsSummaryForBucket2); + } + + private OMUpdateEventBatch processEventBatch() throws IOException { + // Test PUT Event. + // PUT Key6 in Bucket2. + String omPutKey = + OM_KEY_PREFIX + VOL + + OM_KEY_PREFIX + BUCKET_TWO + + OM_KEY_PREFIX + KEY_SIX; + OmKeyInfo omPutKeyInfo = buildOmKeyInfo(VOL, BUCKET_TWO, KEY_SIX, + KEY_SIX, KEY_SIX_OBJECT_ID, BUCKET_TWO_OBJECT_ID, KEY_SIX_SIZE); + keyEvent1 = new OMDBUpdateEvent. + OMUpdateEventBuilder() + .setKey(omPutKey) + .setValue(omPutKeyInfo) + .setTable(omMetadataManager.getKeyTable(getBucketLayout()) + .getName()) + .setAction(OMDBUpdateEvent.OMDBUpdateAction.PUT) + .build(); + // PUT Key7 in Bucket1. + omPutKey = + OM_KEY_PREFIX + VOL + + OM_KEY_PREFIX + BUCKET_ONE + + OM_KEY_PREFIX + KEY_SEVEN; + omPutKeyInfo = buildOmKeyInfo(VOL, BUCKET_ONE, KEY_SEVEN, + KEY_SEVEN, KEY_SEVEN_OBJECT_ID, BUCKET_ONE_OBJECT_ID, KEY_SEVEN_SIZE); + keyEvent2 = new OMDBUpdateEvent. + OMUpdateEventBuilder() + .setKey(omPutKey) + .setValue(omPutKeyInfo) + .setTable(omMetadataManager.getKeyTable(getBucketLayout()) + .getName()) + .setAction(OMDBUpdateEvent.OMDBUpdateAction.PUT) + .build(); + + // Test DELETE Event. + // Delete Key1 in Bucket1. + String omDeleteKey = + OM_KEY_PREFIX + VOL + + OM_KEY_PREFIX + BUCKET_ONE + + OM_KEY_PREFIX + KEY_ONE; + OmKeyInfo omDeleteKeyInfo = buildOmKeyInfo(VOL, BUCKET_ONE, KEY_ONE, + KEY_ONE, KEY_ONE_OBJECT_ID, BUCKET_ONE_OBJECT_ID, KEY_ONE_SIZE); + keyEvent3 = new OMDBUpdateEvent. + OMUpdateEventBuilder() + .setKey(omDeleteKey) + .setTable(omMetadataManager.getKeyTable(getBucketLayout()) + .getName()) + .setValue(omDeleteKeyInfo) + .setAction(OMDBUpdateEvent.OMDBUpdateAction.DELETE) + .build(); + + // Test UPDATE Event. + // Resize Key2 in Bucket1. + String omResizeKey = + OM_KEY_PREFIX + VOL + + OM_KEY_PREFIX + BUCKET_ONE + + OM_KEY_PREFIX + KEY_TWO; + OmKeyInfo oldOmResizeKeyInfo = + buildOmKeyInfo(VOL, BUCKET_ONE, KEY_TWO, KEY_TWO, KEY_TWO_OBJECT_ID, + BUCKET_ONE_OBJECT_ID, KEY_TWO_OLD_SIZE); + OmKeyInfo newOmResizeKeyInfo = + buildOmKeyInfo(VOL, BUCKET_ONE, KEY_TWO, KEY_TWO, KEY_TWO_OBJECT_ID, + BUCKET_ONE_OBJECT_ID, KEY_TWO_OLD_SIZE + 100); + keyEvent4 = new OMDBUpdateEvent. + OMUpdateEventBuilder() + .setKey(omResizeKey) + .setOldValue(oldOmResizeKeyInfo) + .setValue(newOmResizeKeyInfo) + .setTable(omMetadataManager.getKeyTable(getBucketLayout()) + .getName()) + .setAction(OMDBUpdateEvent.OMDBUpdateAction.UPDATE) + .build(); + + return new OMUpdateEventBatch( + Arrays.asList(keyEvent1, keyEvent2, keyEvent3, keyEvent4)); + } + + @Test + public void testProcessForCount() throws IOException { + assertNotNull(nsSummaryForBucket1); + assertEquals(3, nsSummaryForBucket1.getNumOfFiles()); + assertNotNull(nsSummaryForBucket2); + assertEquals(3, nsSummaryForBucket2.getNumOfFiles()); + + Set childDirBucket1 = nsSummaryForBucket1.getChildDir(); + assertEquals(0, childDirBucket1.size()); + Set childDirBucket2 = nsSummaryForBucket2.getChildDir(); + assertEquals(0, childDirBucket2.size()); + } + + @Test + public void testProcessForSize() throws IOException { + assertNotNull(nsSummaryForBucket1); + assertEquals( + KEY_THREE_SIZE + KEY_SEVEN_SIZE + KEY_TWO_OLD_SIZE + 100, + nsSummaryForBucket1.getSizeOfFiles()); + assertNotNull(nsSummaryForBucket2); + assertEquals(KEY_FOUR_SIZE + KEY_FIVE_SIZE + KEY_SIX_SIZE, + nsSummaryForBucket2.getSizeOfFiles()); + } + + + @Test + public void testProcessFileBucketSize() { + int[] fileDistBucket1 = nsSummaryForBucket1.getFileSizeBucket(); + int[] fileDistBucket2 = nsSummaryForBucket2.getFileSizeBucket(); + assertEquals(ReconConstants.NUM_OF_FILE_SIZE_BINS, + fileDistBucket1.length); + assertEquals(ReconConstants.NUM_OF_FILE_SIZE_BINS, + fileDistBucket2.length); + + // Check for 1's and 0's in fileDistBucket1 + int[] expectedIndexes1 = {1, 3, 40}; + for (int index = 0; index < fileDistBucket1.length; index++) { + if (contains(expectedIndexes1, index)) { + assertEquals(1, fileDistBucket1[index]); + } else { + assertEquals(0, fileDistBucket1[index]); + } + } + + // Check for 1's and 0's in fileDistBucket2 + int[] expectedIndexes2 = {0, 2, 3}; + for (int index = 0; index < fileDistBucket2.length; index++) { + if (contains(expectedIndexes2, index)) { + assertEquals(1, fileDistBucket2[index]); + } else { + assertEquals(0, fileDistBucket2[index]); + } + } + } + + } + + /** + * Populate OMDB with the following configs. + * vol + * / \ + * bucket1 bucket2 + * / \ \ \ \ + * key1 key2 key3 key4 key5 + * + * @throws IOException + */ + private static void populateOMDB() throws IOException { + writeKeyToOm(reconOMMetadataManager, + KEY_ONE, + BUCKET_ONE, + VOL, + KEY_ONE, + KEY_ONE_OBJECT_ID, + BUCKET_ONE_OBJECT_ID, + BUCKET_ONE_OBJECT_ID, + VOL_OBJECT_ID, + KEY_ONE_SIZE, + getBucketLayout()); + writeKeyToOm(reconOMMetadataManager, + KEY_TWO, + BUCKET_ONE, + VOL, + KEY_TWO, + KEY_TWO_OBJECT_ID, + BUCKET_ONE_OBJECT_ID, + BUCKET_ONE_OBJECT_ID, + VOL_OBJECT_ID, + KEY_TWO_OLD_SIZE, + getBucketLayout()); + writeKeyToOm(reconOMMetadataManager, + KEY_THREE, + BUCKET_ONE, + VOL, + KEY_THREE, + KEY_THREE_OBJECT_ID, + BUCKET_ONE_OBJECT_ID, + BUCKET_ONE_OBJECT_ID, + VOL_OBJECT_ID, + KEY_THREE_SIZE, + getBucketLayout()); + + writeKeyToOm(reconOMMetadataManager, + KEY_FOUR, + BUCKET_TWO, + VOL, + KEY_FOUR, + KEY_FOUR_OBJECT_ID, + BUCKET_TWO_OBJECT_ID, + BUCKET_TWO_OBJECT_ID, + VOL_OBJECT_ID, + KEY_FOUR_SIZE, + getBucketLayout()); + writeKeyToOm(reconOMMetadataManager, + KEY_FIVE, + BUCKET_TWO, + VOL, + KEY_FIVE, + KEY_FIVE_OBJECT_ID, + PARENT_OBJECT_ID_ZERO, + BUCKET_TWO_OBJECT_ID, + VOL_OBJECT_ID, + KEY_FIVE_SIZE, + getBucketLayout()); + } + + /** + * Create a new OM Metadata manager instance with one user, one vol, and two + * buckets. + * + * @throws IOException ioEx + */ + private static void initializeNewOmMetadataManager( + File omDbDir) + throws IOException { + omConfiguration = new OzoneConfiguration(); + omConfiguration.set(OZONE_OM_DB_DIRS, + omDbDir.getAbsolutePath()); + omConfiguration.set(OMConfigKeys + .OZONE_OM_ENABLE_FILESYSTEM_PATHS, "true"); + omMetadataManager = new OmMetadataManagerImpl( + omConfiguration, null); + + String volumeKey = omMetadataManager.getVolumeKey(VOL); + OmVolumeArgs args = + OmVolumeArgs.newBuilder() + .setObjectID(VOL_OBJECT_ID) + .setVolume(VOL) + .setAdminName(TEST_USER) + .setOwnerName(TEST_USER) + .build(); + omMetadataManager.getVolumeTable().put(volumeKey, args); + + OmBucketInfo bucketInfo1 = OmBucketInfo.newBuilder() + .setVolumeName(VOL) + .setBucketName(BUCKET_ONE) + .setObjectID(BUCKET_ONE_OBJECT_ID) + .setBucketLayout(getBucketLayout()) + .build(); + + OmBucketInfo bucketInfo2 = OmBucketInfo.newBuilder() + .setVolumeName(VOL) + .setBucketName(BUCKET_TWO) + .setObjectID(BUCKET_TWO_OBJECT_ID) + .setBucketLayout(getBucketLayout()) + .build(); + + String bucketKey = omMetadataManager.getBucketKey( + bucketInfo1.getVolumeName(), bucketInfo1.getBucketName()); + String bucketKey2 = omMetadataManager.getBucketKey( + bucketInfo2.getVolumeName(), bucketInfo2.getBucketName()); + + omMetadataManager.getBucketTable().put(bucketKey, bucketInfo1); + omMetadataManager.getBucketTable().put(bucketKey2, bucketInfo2); + } + + /** + * Build a key info for put/update action. + * @param volume volume name + * @param bucket bucket name + * @param key key name + * @param fileName file name + * @param objectID object ID + * @param parentObjectId parent object ID + * @param dataSize file size + * @return the KeyInfo + */ + private static OmKeyInfo buildOmKeyInfo(String volume, + String bucket, + String key, + String fileName, + long objectID, + long parentObjectId, + long dataSize) { + return new OmKeyInfo.Builder() + .setBucketName(bucket) + .setVolumeName(volume) + .setKeyName(key) + .setFileName(fileName) + .setReplicationConfig( + StandaloneReplicationConfig.getInstance( + HddsProtos.ReplicationFactor.ONE)) + .setObjectID(objectID) + .setParentObjectID(parentObjectId) + .setDataSize(dataSize) + .build(); + } + + // Helper method to check if an array contains a specific value + private boolean contains(int[] arr, int value) { + for (int num : arr) { + if (num == value) { + return true; + } + } + return false; + } + + private static BucketLayout getBucketLayout() { + return BucketLayout.OBJECT_STORE; + } +} diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpoint.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpoint.java index d2fd56bac9f4..62a3494bbffd 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpoint.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpoint.java @@ -58,7 +58,6 @@ import org.apache.hadoop.ozone.client.OzoneKeyDetails; import org.apache.hadoop.ozone.client.OzoneMultipartUploadPartListParts; import org.apache.hadoop.ozone.client.OzoneVolume; -import org.apache.hadoop.ozone.client.io.KeyMetadataAware; import org.apache.hadoop.ozone.client.io.KeyOutputStream; import org.apache.hadoop.ozone.client.io.OzoneInputStream; import org.apache.hadoop.ozone.client.io.OzoneOutputStream; @@ -1001,12 +1000,10 @@ private Response createMultipartKey(OzoneVolume volume, String bucket, metadataLatencyNs = getMetrics().updatePutKeyMetadataStats(startNanos); putLength = IOUtils.copyLarge(digestInputStream, ozoneOutputStream); - ((KeyMetadataAware)ozoneOutputStream.getOutputStream()) - .getMetadata().put(ETAG, DatatypeConverter.printHexBinary( - digestInputStream.getMessageDigest().digest()) - .toLowerCase()); - keyOutputStream - = ozoneOutputStream.getKeyOutputStream(); + byte[] digest = digestInputStream.getMessageDigest().digest(); + ozoneOutputStream.getMetadata() + .put(ETAG, DatatypeConverter.printHexBinary(digest).toLowerCase()); + keyOutputStream = ozoneOutputStream.getKeyOutputStream(); } getMetrics().incPutKeySuccessLength(putLength); perf.appendSizeBytes(putLength); diff --git a/pom.xml b/pom.xml index 2c6b8baf3f32..dd6dd7c75827 100644 --- a/pom.xml +++ b/pom.xml @@ -75,10 +75,10 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xs ${hdds.version} - 3.0.1 + 3.1.0 - 1.0.5 + 1.0.6 2.3.0 @@ -141,7 +141,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xs 3.12.2 5.0.4 0.8.0.RELEASE - 1.77 + 1.78.1 3.2.0 10.14.2.0 3.0.2 @@ -184,6 +184,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xs 3.4.2 1.2.22 + 1.0.1 1.9.21 1.8 4.7.5 @@ -235,7 +236,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xs - 4.1.100.Final + 4.1.109.Final 1.58.0 7.7.3 @@ -255,7 +256,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xs true - -Xmx4096m -XX:+HeapDumpOnOutOfMemoryError + -Xmx8192m -XX:+HeapDumpOnOutOfMemoryError flaky | org.apache.ozone.test.FlakyTest | slow | org.apache.ozone.test.SlowTest | unhealthy | org.apache.ozone.test.UnhealthyTest 3.0.0-M4 ${maven-surefire-plugin.version} @@ -265,7 +266,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xs 3.1 3.1.1 3.1.0 - 3.4.1 + 3.5.1 2.5 3.4.0 3.3.0 @@ -277,7 +278,8 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xs 0.12 2.8.1 1.9 - 3.0.2 + 3.7.1 + 4.2.2 0.29.0 1.3.1 2.3.0 @@ -289,7 +291,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xs 9.3 1200 1.12.661 - 1.12.0 + 1.15.0 ${hadoop.version} @@ -310,6 +312,9 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xs 1.4.9 1.4.0 + 5.3.34 + 3.11.10 + 5.1.0 1.1.10.5 @@ -784,6 +789,16 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xs httpcore-nio ${httpcore.version} + + org.apache.kerby + kerb-core + ${kerby.version} + + + org.apache.kerby + kerb-util + ${kerby.version} + commons-codec commons-codec @@ -1035,8 +1050,13 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xs ${ratis.version} + org.apache.ratis ratis-client + ${ratis.version} + + org.apache.ratis + ratis-server-api ${ratis.version} @@ -1763,6 +1783,13 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xs org.apache.maven.plugins maven-dependency-plugin ${maven-dependency-plugin.version} + + + org.codehaus.plexus + plexus-archiver + ${plexus-archiver.version} + + org.apache.maven.plugins @@ -1811,6 +1838,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xs org.rocksdb.OptionsUtil org.rocksdb.RocksDBException org.rocksdb.StatsLevel + org.rocksdb.TableProperties org.rocksdb.TransactionLogIterator.BatchResult org.rocksdb.TickerType org.rocksdb.LiveFileMetaData @@ -1823,7 +1851,10 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xs org.rocksdb.RocksDB.* - org.apache.hadoop.hdds.utils.db.managed.* + + org.apache.hadoop.hdds.utils.db.managed.* + org.apache.ozone.rocksdiff.RocksDBCheckpointDiffer + true @@ -1834,6 +1865,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xs true + Disable with @Unhealthy or @Slow instead (see HDDS-9276) org.junit.Ignore