This is an automated email from the ASF dual-hosted git repository.

szetszwo pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git


The following commit(s) were added to refs/heads/master by this push:
     new 247f5cab1f6 HDDS-15758. Commit PutBlock without Raft in Client (#10823)
247f5cab1f6 is described below

commit 247f5cab1f6f7b15284bb51c09d699836e385551
Author: Rui Wang <[email protected]>
AuthorDate: Thu Jul 30 00:31:17 2026 +0800

    HDDS-15758. Commit PutBlock without Raft in Client (#10823)
---
 .../apache/hadoop/hdds/scm/OzoneClientConfig.java  | 16 +++++++
 .../hdds/scm/storage/BlockDataStreamOutput.java    | 28 ++++++++++-
 .../hadoop/hdds/scm/TestOzoneClientConfig.java     | 17 +++++++
 .../hdds/scm/storage/MockDatanodePipeline.java     | 29 +++++++++++-
 .../scm/storage/TestBlockDataStreamOutput.java     | 22 ++++++++-
 .../java/org/apache/hadoop/hdds/HddsUtils.java     |  1 +
 .../org/apache/hadoop/ozone/audit/DNAction.java    |  1 +
 .../container/common/impl/HddsDispatcher.java      |  4 +-
 .../ozone/container/keyvalue/KeyValueHandler.java  |  1 +
 .../container/keyvalue/impl/BlockManagerImpl.java  | 14 ++++++
 .../client/rpc/TestBlockDataStreamOutput.java      | 55 +++++++++++++++-------
 .../rpc/TestContainerStateMachineStream.java       | 30 +++++++++---
 12 files changed, 187 insertions(+), 31 deletions(-)

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 dba10525d2b..70eae2073ff 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
@@ -289,6 +289,14 @@ public class OzoneClientConfig {
           tags = ConfigTag.CLIENT)
   private boolean enablePutblockPiggybacking = false;
 
+  @Config(key = "ozone.client.datastream.putblock.on.close.enabled",
+      defaultValue = "false",
+      type = ConfigType.BOOLEAN,
+      description = "When enabled, use StreamInitWithPutBlock so datanodes 
commit PutBlock " +
+          "when the Ratis data stream closes instead of via a separate 
WriteAsync PutBlock.",
+      tags = ConfigTag.CLIENT)
+  private boolean datastreamPutBlockOnCloseEnabled = false;
+
   @Config(key = "ozone.client.key.write.concurrency",
       defaultValue = "1",
       description = "Maximum concurrent writes allowed on each key. " +
@@ -646,6 +654,14 @@ public void setStreamReadTimeout(Duration 
streamReadTimeout) {
     this.streamReadTimeout = streamReadTimeout;
   }
 
+  public boolean isDatastreamPutBlockOnCloseEnabled() {
+    return datastreamPutBlockOnCloseEnabled;
+  }
+
+  public void setDatastreamPutBlockOnCloseEnabled(boolean 
datastreamPutBlockOnCloseEnabled) {
+    this.datastreamPutBlockOnCloseEnabled = datastreamPutBlockOnCloseEnabled;
+  }
+
   /**
    * Enum for indicating what mode to use when combining chunk and block
    * checksums to define an aggregate FileChecksum. This should be considered
diff --git 
a/hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/storage/BlockDataStreamOutput.java
 
b/hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/storage/BlockDataStreamOutput.java
index 1ababc7a1d7..e3fff7528d4 100644
--- 
a/hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/storage/BlockDataStreamOutput.java
+++ 
b/hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/storage/BlockDataStreamOutput.java
@@ -211,9 +211,12 @@ private DataStreamOutput setupStream(Pipeline pipeline) 
throws IOException {
     // TODO: The datanode UUID is not used meaningfully, consider deprecating
     //  it or remove it completely if possible
     String id = pipeline.getFirstNode().getUuidString();
+    ContainerProtos.Type streamInitType = 
config.isDatastreamPutBlockOnCloseEnabled()
+        ? ContainerProtos.Type.StreamInitWithPutBlock
+        : ContainerProtos.Type.StreamInit;
     ContainerProtos.ContainerCommandRequestProto.Builder builder =
         ContainerProtos.ContainerCommandRequestProto.newBuilder()
-            .setCmdType(ContainerProtos.Type.StreamInit)
+            .setCmdType(streamInitType)
             .setContainerID(blockID.get().getContainerID())
             .setDatanodeUuid(id).setWriteChunk(writeChunkRequest);
 
@@ -416,6 +419,10 @@ public void executePutBlock(boolean close,
       byteBufferList = null;
     }
     waitFuturesComplete();
+    if (close && config.isDatastreamPutBlockOnCloseEnabled()) {
+      // Wait for boundary PutBlock(s) before appending the stream-close 
PutBlock.
+      waitPutBlockFuturesComplete();
+    }
     final BlockData blockData = containerBlockData.build();
     if (close) {
       // HDDS-12007 changed datanodes to ignore the following PutBlock request.
@@ -437,8 +444,12 @@ public void executePutBlock(boolean close,
           }
         }
       });
+      if (config.isDatastreamPutBlockOnCloseEnabled()) {
+        // PutBlock is supposed to be committed after the data stream close so 
there
+        // is no need to continue.
+        return;
+      }
     }
-
     try {
       XceiverClientReply asyncReply =
           putBlockAsync(xceiverClient, blockData, close, tokenString);
@@ -545,6 +556,19 @@ public void waitFuturesComplete() throws IOException {
     }
   }
 
+  private void waitPutBlockFuturesComplete() throws IOException {
+    if (putBlockFutures.isEmpty()) {
+      return;
+    }
+    try {
+      
CompletableFuture.allOf(putBlockFutures.toArray(EMPTY_FUTURE_ARRAY)).get();
+      checkOpen();
+    } catch (Exception e) {
+      LOG.warn("Failed to commit PutBlock before stream close: " + e);
+      throw new IOException(e);
+    }
+  }
+
   /**
    * @param close whether the flush is happening as part of closing the stream
    */
diff --git 
a/hadoop-hdds/client/src/test/java/org/apache/hadoop/hdds/scm/TestOzoneClientConfig.java
 
b/hadoop-hdds/client/src/test/java/org/apache/hadoop/hdds/scm/TestOzoneClientConfig.java
index 5c1eeccff91..cb05d31a2d6 100644
--- 
a/hadoop-hdds/client/src/test/java/org/apache/hadoop/hdds/scm/TestOzoneClientConfig.java
+++ 
b/hadoop-hdds/client/src/test/java/org/apache/hadoop/hdds/scm/TestOzoneClientConfig.java
@@ -91,4 +91,21 @@ public void testStreamReadConfigParsing() {
     assertEquals(2 << 20, clientConfig.getStreamReadResponseDataSize());
     assertEquals(Duration.ofSeconds(5), clientConfig.getStreamReadTimeout());
   }
+
+  @Test
+  void testDatastreamPutBlockOnCloseEnabledDefault() {
+    OzoneClientConfig subject = new OzoneConfiguration()
+        .getObject(OzoneClientConfig.class);
+    assertFalse(subject.isDatastreamPutBlockOnCloseEnabled());
+  }
+
+  @Test
+  void testDatastreamPutBlockOnCloseConfigParsing() {
+    OzoneConfiguration conf = new OzoneConfiguration();
+    conf.setBoolean("ozone.client.datastream.putblock.on.close.enabled", true);
+
+    OzoneClientConfig subject = conf.getObject(OzoneClientConfig.class);
+
+    assertTrue(subject.isDatastreamPutBlockOnCloseEnabled());
+  }
 }
diff --git 
a/hadoop-hdds/client/src/test/java/org/apache/hadoop/hdds/scm/storage/MockDatanodePipeline.java
 
b/hadoop-hdds/client/src/test/java/org/apache/hadoop/hdds/scm/storage/MockDatanodePipeline.java
index 85341fd2721..c1c11b0d1a8 100644
--- 
a/hadoop-hdds/client/src/test/java/org/apache/hadoop/hdds/scm/storage/MockDatanodePipeline.java
+++ 
b/hadoop-hdds/client/src/test/java/org/apache/hadoop/hdds/scm/storage/MockDatanodePipeline.java
@@ -43,6 +43,7 @@
 import 
org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.PutBlockResponseProto;
 import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.Result;
 import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.Type;
+import org.apache.hadoop.hdds.ratis.ContainerCommandRequestMessage;
 import org.apache.hadoop.hdds.scm.XceiverClientFactory;
 import org.apache.hadoop.hdds.scm.XceiverClientManager;
 import org.apache.hadoop.hdds.scm.XceiverClientRatis;
@@ -56,6 +57,7 @@
 import org.apache.ratis.io.WriteOption;
 import org.apache.ratis.protocol.DataStreamReply;
 import org.apache.ratis.protocol.RoutingTable;
+import org.apache.ratis.thirdparty.com.google.protobuf.ByteString;
 
 /**
  * A stateful test harness that simulates a datanode pipeline for {@link 
BlockDataStreamOutput} unit tests.
@@ -76,6 +78,7 @@ public class MockDatanodePipeline {
   private final List<byte[]> receivedChunks = Collections.synchronizedList(new 
ArrayList<>());
   private final List<ContainerCommandRequestProto> receivedPutBlocks = 
Collections.synchronizedList(new ArrayList<>());
   private final AtomicInteger watchForCommitCount = new AtomicInteger(0);
+  private volatile Type streamInitType;
 
   // Commit tracking
   private final AtomicLong nextLogIndex = new AtomicLong(1);
@@ -120,8 +123,14 @@ public MockDatanodePipeline(BlockID blockID) throws 
IOException {
     // Both overloads must be stubbed: stream(ByteBuffer) and 
stream(ByteBuffer, RoutingTable) — the pipeline-mode
     // default is true, so the 2-arg overload is what 
BlockDataStreamOutput.setupStream calls.
     DataStreamApi dataStreamApi = mock(DataStreamApi.class);
-    
doReturn(mockDataStreamOutput).when(dataStreamApi).stream(any(ByteBuffer.class));
-    
doReturn(mockDataStreamOutput).when(dataStreamApi).stream(any(ByteBuffer.class),
 any(RoutingTable.class));
+    doAnswer(invocation -> {
+      captureStreamInitType(invocation.getArgument(0));
+      return mockDataStreamOutput;
+    }).when(dataStreamApi).stream(any(ByteBuffer.class));
+    doAnswer(invocation -> {
+      captureStreamInitType(invocation.getArgument(0));
+      return mockDataStreamOutput;
+    }).when(dataStreamApi).stream(any(ByteBuffer.class), 
any(RoutingTable.class));
     doReturn(dataStreamApi).when(xceiverClient).getDataStreamApi();
 
     // Setup sendCommandAsync (putBlock) behavior
@@ -229,6 +238,10 @@ public int getWatchForCommitCount() {
     return watchForCommitCount.get();
   }
 
+  public Type getStreamInitType() {
+    return streamInitType;
+  }
+
   /** Concatenate all received chunks into a single byte array. */
   public byte[] getAllReceivedData() {
     int total = receivedChunks.stream().mapToInt(c -> c.length).sum();
@@ -263,6 +276,18 @@ public MockDatanodePipeline failWatchAfter(int n, 
Supplier<Throwable> err) {
 
   // --- Helpers ---
 
+  private void captureStreamInitType(ByteBuffer buffer) {
+    ByteBuffer dup = buffer.duplicate();
+    byte[] bytes = new byte[dup.remaining()];
+    dup.get(bytes);
+    try {
+      streamInitType = ContainerCommandRequestMessage.toProto(
+          ByteString.copyFrom(bytes), null).getCmdType();
+    } catch (Exception e) {
+      throw new IllegalStateException("Failed to decode stream init request", 
e);
+    }
+  }
+
   private static ContainerCommandResponseProto buildPutBlockResponse(BlockID 
blockID) {
     return ContainerCommandResponseProto.newBuilder()
         .setCmdType(Type.PutBlock)
diff --git 
a/hadoop-hdds/client/src/test/java/org/apache/hadoop/hdds/scm/storage/TestBlockDataStreamOutput.java
 
b/hadoop-hdds/client/src/test/java/org/apache/hadoop/hdds/scm/storage/TestBlockDataStreamOutput.java
index 7634328c56f..8f51e3c9a41 100644
--- 
a/hadoop-hdds/client/src/test/java/org/apache/hadoop/hdds/scm/storage/TestBlockDataStreamOutput.java
+++ 
b/hadoop-hdds/client/src/test/java/org/apache/hadoop/hdds/scm/storage/TestBlockDataStreamOutput.java
@@ -30,8 +30,11 @@
 import java.util.concurrent.CompletionException;
 import org.apache.commons.lang3.RandomUtils;
 import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos;
+import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.Type;
 import org.apache.hadoop.hdds.scm.OzoneClientConfig;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
 
 /**
  * Unit tests for {@link BlockDataStreamOutput} exercised through the {@link 
ByteBufferStreamOutput} interface with a
@@ -59,16 +62,33 @@ private static OzoneClientConfig createConfig() {
   }
 
   private BlockDataStreamOutput createStream(MockDatanodePipeline pipeline) 
throws IOException {
+    return createStream(pipeline, createConfig());
+  }
+
+  private BlockDataStreamOutput createStream(
+      MockDatanodePipeline pipeline, OzoneClientConfig config) throws 
IOException {
     List<StreamBuffer> bufferList = new ArrayList<>();
     return new BlockDataStreamOutput(
         pipeline.getBlockID(),
         pipeline.getClientFactory(),
         pipeline.getPipeline(),
-        createConfig(),
+        config,
         null,  // no token
         bufferList);
   }
 
+  @ParameterizedTest
+  @ValueSource(booleans = {false, true})
+  void streamInitTypeFollowsClientConfig(boolean putBlockOnCloseEnabled) 
throws Exception {
+    MockDatanodePipeline pipeline = new MockDatanodePipeline();
+    OzoneClientConfig config = createConfig();
+    config.setDatastreamPutBlockOnCloseEnabled(putBlockOnCloseEnabled);
+    try (BlockDataStreamOutput stream = createStream(pipeline, config)) {
+      Type expected = putBlockOnCloseEnabled ? Type.StreamInitWithPutBlock : 
Type.StreamInit;
+      assertEquals(expected, pipeline.getStreamInitType());
+    }
+  }
+
   @Test
   void writeSubChunkThenClose() throws Exception {
     MockDatanodePipeline pipeline = new MockDatanodePipeline();
diff --git 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HddsUtils.java 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HddsUtils.java
index 46289059dbb..3e6715b11c3 100644
--- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HddsUtils.java
+++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HddsUtils.java
@@ -373,6 +373,7 @@ public static boolean isReadOnly(
     case PutBlock:
     case PutSmallFile:
     case StreamInit:
+    case StreamInitWithPutBlock:
     case StreamWrite:
     case FinalizeBlock:
       return false;
diff --git 
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/audit/DNAction.java
 
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/audit/DNAction.java
index 61d1c49da04..c5a62d8e79f 100644
--- 
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/audit/DNAction.java
+++ 
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/audit/DNAction.java
@@ -41,6 +41,7 @@ public enum DNAction implements AuditAction {
   CLOSE_CONTAINER,
   GET_COMMITTED_BLOCK_LENGTH,
   STREAM_INIT,
+  STREAM_INIT_WITH_PUT_BLOCK,
   FINALIZE_BLOCK,
   ECHO,
   GET_CONTAINER_CHECKSUM_INFO,
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 47d29832587..1564e3ddbf5 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
@@ -226,7 +226,8 @@ private ContainerCommandResponseProto dispatchRequest(
         (cmdType == Type.WriteChunk && dispatcherContext != null
             && dispatcherContext.getStage()
             == DispatcherContext.WriteChunkStage.WRITE_DATA)
-            || (cmdType == Type.StreamInit);
+            || (cmdType == Type.StreamInit)
+            || (cmdType == Type.StreamInitWithPutBlock);
     boolean isWriteCommitStage =
         (cmdType == Type.WriteChunk && dispatcherContext != null
             && dispatcherContext.getStage()
@@ -925,6 +926,7 @@ private static DNAction getAuditAction(Type cmdType) {
     case CloseContainer   : return DNAction.CLOSE_CONTAINER;
     case GetCommittedBlockLength : return DNAction.GET_COMMITTED_BLOCK_LENGTH;
     case StreamInit       : return DNAction.STREAM_INIT;
+    case StreamInitWithPutBlock: return DNAction.STREAM_INIT_WITH_PUT_BLOCK;
     case FinalizeBlock    : return DNAction.FINALIZE_BLOCK;
     case Echo             : return DNAction.ECHO;
     case GetContainerChecksumInfo: return DNAction.GET_CONTAINER_CHECKSUM_INFO;
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 18486bc9ef3..66163be780d 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
@@ -355,6 +355,7 @@ static ContainerCommandResponseProto 
dispatchRequest(KeyValueHandler handler,
     case WriteChunk:
       return handler.handleWriteChunk(request, kvContainer, dispatcherContext);
     case StreamInit:
+    case StreamInitWithPutBlock:
       return handler.handleStreamInit(request, kvContainer, dispatcherContext);
     case ListChunk:
       return handler.handleUnsupportedOp(request);
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 62dbcbe808e..46929633d09 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
@@ -209,6 +209,20 @@ public long persistPutBlock(KeyValueContainer container,
       // container to determine whether the blockCount is already incremented
       // for this block in the DB or not.
       long localID = data.getLocalID();
+      // For the PutBlock that is endOfBlock and meanwhile bscId = 0, it means
+      // this PutBlock comes from data stream close without going through the
+      // Raft, thus there is no log index. In this case, we should not let
+      // 0 to overwrite previous possible PutBlocks from Ratis log that were
+      // generated during immediate flushes from the active data stream. 
Instead,
+      // we should load the latest bscid and reuse that id.
+      if (endOfBlock && bcsId == 0) {
+        BlockData existing = db.getStore().getBlockDataTable()
+            .get(containerData.getBlockKey(localID));
+        if (existing != null) {
+          bcsId = existing.getBlockCommitSequenceId();
+          data.setBlockCommitSequenceId(bcsId);
+        }
+      }
       boolean isBlockInCache = 
container.isBlockInPendingPutBlockCache(localID);
       boolean incrBlockCount = false;
 
diff --git 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/TestBlockDataStreamOutput.java
 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/TestBlockDataStreamOutput.java
index 3cea0590d85..b4337a494e1 100644
--- 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/TestBlockDataStreamOutput.java
+++ 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/TestBlockDataStreamOutput.java
@@ -152,8 +152,10 @@ static MiniOzoneCluster createCluster() throws IOException,
 
   private static Stream<Arguments> clientParameters() {
     return Stream.of(
-        Arguments.of(true),
-        Arguments.of(false)
+        Arguments.of(true, true),
+        Arguments.of(true, false),
+        Arguments.of(false, true),
+        Arguments.of(false, false)
     );
   }
 
@@ -166,11 +168,19 @@ private static Stream<Arguments> dataLengthParameters() {
     );
   }
 
+  private static Stream<Arguments> streamWriteParameters() {
+    return dataLengthParameters().flatMap(dataLength ->
+        Stream.of(true, false).map(putBlockOnCloseEnabled ->
+            Arguments.of(dataLength.get()[0], putBlockOnCloseEnabled)));
+  }
+
   static OzoneClientConfig newClientConfig(ConfigurationSource source,
-                                           boolean flushDelay) {
+                                           boolean flushDelay,
+                                           boolean putBlockOnCloseEnabled) {
     OzoneClientConfig clientConfig = source.getObject(OzoneClientConfig.class);
     clientConfig.setChecksumType(ContainerProtos.ChecksumType.NONE);
     clientConfig.setStreamBufferFlushDelay(flushDelay);
+    clientConfig.setDatastreamPutBlockOnCloseEnabled(putBlockOnCloseEnabled);
     return clientConfig;
   }
 
@@ -198,13 +208,17 @@ public void shutdown() {
   }
 
   @ParameterizedTest
-  @MethodSource("dataLengthParameters")
+  @MethodSource("streamWriteParameters")
   @Flaky("HDDS-12027")
-  public void testStreamWrite(int dataLength) throws Exception {
-    OzoneClientConfig config = newClientConfig(cluster.getConf(), false);
+  public void testStreamWrite(int dataLength, boolean putBlockOnCloseEnabled) 
throws Exception {
+    OzoneClientConfig config = newClientConfig(cluster.getConf(), false, 
putBlockOnCloseEnabled);
     try (OzoneClient client = newClient(cluster.getConf(), config)) {
       testWrite(client, dataLength);
-      testWriteWithFailure(client, dataLength);
+      // Forced container close before stream close relies on async PutBlock 
recovery;
+      // that path is not used when PutBlock is committed only on data stream 
close.
+      if (!putBlockOnCloseEnabled) {
+        testWriteWithFailure(client, dataLength);
+      }
     }
   }
 
@@ -253,8 +267,9 @@ static void validateData(OzoneClient client, String 
keyName, byte[] data) throws
 
   @ParameterizedTest
   @MethodSource("clientParameters")
-  public void testPutBlockAtBoundary(boolean flushDelay) throws Exception {
-    OzoneClientConfig config = newClientConfig(cluster.getConf(), flushDelay);
+  public void testPutBlockAtBoundary(boolean flushDelay, boolean 
putBlockOnCloseEnabled)
+      throws Exception {
+    OzoneClientConfig config = newClientConfig(cluster.getConf(), flushDelay, 
putBlockOnCloseEnabled);
     try (OzoneClient client = newClient(cluster.getConf(), config)) {
       int dataLength = 500;
       XceiverClientMetrics metrics =
@@ -273,19 +288,21 @@ public void testPutBlockAtBoundary(boolean flushDelay) 
throws Exception {
       
assertThat(metrics.getPendingContainerOpCountMetrics(ContainerProtos.Type.PutBlock))
           .isLessThanOrEqualTo(pendingPutBlockCount + 1);
       key.close();
-      // Since data length is 500 , first putBlock will be at 400(flush 
boundary)
-      // and the other at 500
+      // Since data length is 500, first putBlock will be at 400 (flush 
boundary).
+      // Close commits via WriteAsync PutBlock only when putBlockOnClose is 
disabled.
+      int expectedPutBlocks = putBlockOnCloseEnabled ? 1 : 2;
       assertEquals(
           metrics.getContainerOpCountMetrics(ContainerProtos.Type.PutBlock),
-          putBlockCount + 2);
+          putBlockCount + expectedPutBlocks);
       validateData(client, keyName, data);
     }
   }
 
   @ParameterizedTest
   @MethodSource("clientParameters")
-  public void testMinPacketSize(boolean flushDelay) throws Exception {
-    OzoneClientConfig config = newClientConfig(cluster.getConf(), flushDelay);
+  public void testMinPacketSize(boolean flushDelay, boolean 
putBlockOnCloseEnabled)
+      throws Exception {
+    OzoneClientConfig config = newClientConfig(cluster.getConf(), flushDelay, 
putBlockOnCloseEnabled);
     try (OzoneClient client = newClient(cluster.getConf(), config)) {
       String keyName = getKeyName();
       XceiverClientMetrics metrics =
@@ -312,8 +329,9 @@ public void testMinPacketSize(boolean flushDelay) throws 
Exception {
 
   @ParameterizedTest
   @MethodSource("clientParameters")
-  public void testTotalAckDataLength(boolean flushDelay) throws Exception {
-    OzoneClientConfig config = newClientConfig(cluster.getConf(), flushDelay);
+  public void testTotalAckDataLength(boolean flushDelay, boolean 
putBlockOnCloseEnabled)
+      throws Exception {
+    OzoneClientConfig config = newClientConfig(cluster.getConf(), flushDelay, 
putBlockOnCloseEnabled);
     try (OzoneClient client = newClient(cluster.getConf(), config)) {
       int dataLength = 400;
       String keyName = getKeyName();
@@ -334,8 +352,9 @@ public void testTotalAckDataLength(boolean flushDelay) 
throws Exception {
 
   @ParameterizedTest
   @MethodSource("clientParameters")
-  public void testDatanodeVersion(boolean flushDelay) throws Exception {
-    OzoneClientConfig config = newClientConfig(cluster.getConf(), flushDelay);
+  public void testDatanodeVersion(boolean flushDelay, boolean 
putBlockOnCloseEnabled)
+      throws Exception {
+    OzoneClientConfig config = newClientConfig(cluster.getConf(), flushDelay, 
putBlockOnCloseEnabled);
     try (OzoneClient client = newClient(cluster.getConf(), config)) {
       // Verify all DNs internally have versions set correctly
       List<HddsDatanodeService> dns = cluster.getHddsDatanodes();
diff --git 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/TestContainerStateMachineStream.java
 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/TestContainerStateMachineStream.java
index 58af0db2b11..8f8bc5cf946 100644
--- 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/TestContainerStateMachineStream.java
+++ 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/TestContainerStateMachineStream.java
@@ -26,11 +26,15 @@
 import java.nio.ByteBuffer;
 import java.util.List;
 import java.util.UUID;
+import java.util.stream.Stream;
 import org.apache.hadoop.conf.StorageUnit;
 import org.apache.hadoop.hdds.client.ReplicationType;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.hdds.scm.OzoneClientConfig;
 import org.apache.hadoop.hdds.utils.IOUtils;
 import org.apache.hadoop.ozone.client.ObjectStore;
 import org.apache.hadoop.ozone.client.OzoneClient;
+import org.apache.hadoop.ozone.client.OzoneClientFactory;
 import org.apache.hadoop.ozone.client.io.KeyDataStreamOutput;
 import org.apache.hadoop.ozone.client.io.OzoneDataStreamOutput;
 import org.apache.hadoop.ozone.container.ContainerTestHelper;
@@ -40,7 +44,8 @@
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.TestInstance;
 import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.ValueSource;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
 
 /**
  * Tests the containerStateMachine stream handling.
@@ -48,7 +53,6 @@
 @TestInstance(TestInstance.Lifecycle.PER_CLASS)
 public abstract class TestContainerStateMachineStream implements 
NonHATests.TestCase {
   private OzoneClient client;
-  private ObjectStore objectStore;
   private String volumeName;
   private String bucketName;
   private int chunkSize;
@@ -58,7 +62,7 @@ void setup() throws Exception {
     chunkSize = (int) 
cluster().getConf().getStorageSize(OZONE_SCM_CHUNK_SIZE_KEY, 1024 * 1024, 
StorageUnit.BYTES);
 
     client = cluster().newClient();
-    objectStore = client.getObjectStore();
+    ObjectStore objectStore = client.getObjectStore();
 
     volumeName = "vol-" + UUID.randomUUID();
     bucketName = "teststreambucket";
@@ -71,14 +75,26 @@ void shutdown() {
     IOUtils.closeQuietly(client);
   }
 
+  private static Stream<Arguments> streamingParameters() {
+    return Stream.of(-1, +1).flatMap(offset ->
+        Stream.of(false, true).map(putBlockOnCloseEnabled ->
+            Arguments.of(offset, putBlockOnCloseEnabled)));
+  }
+
   @ParameterizedTest
-  @ValueSource(ints = {-1, +1})
-  void testContainerStateMachineForStreaming(int offset) throws Exception {
+  @MethodSource("streamingParameters")
+  void testContainerStateMachineForStreaming(int offset, boolean 
putBlockOnCloseEnabled)
+      throws Exception {
     final int size = chunkSize + offset;
+    OzoneConfiguration conf = new OzoneConfiguration(cluster().getConf());
+    OzoneClientConfig clientConfig = conf.getObject(OzoneClientConfig.class);
+    clientConfig.setDatastreamPutBlockOnCloseEnabled(putBlockOnCloseEnabled);
+    conf.setFromObject(clientConfig);
 
     final List<OmKeyLocationInfo> locationInfoList;
-    try (OzoneDataStreamOutput key = createStreamKey("key" + offset, 
ReplicationType.RATIS, size,
-        objectStore, volumeName, bucketName)) {
+    try (OzoneClient streamingClient = OzoneClientFactory.getRpcClient(conf);
+         OzoneDataStreamOutput key = createStreamKey("key" + offset + "-" + 
putBlockOnCloseEnabled,
+             ReplicationType.RATIS, size, streamingClient.getObjectStore(), 
volumeName, bucketName)) {
 
       byte[] data = ContainerTestHelper.generateData(size, true);
       key.write(ByteBuffer.wrap(data));


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to