This is an automated email from the ASF dual-hosted git repository.
adoroszlai 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 eb178061112 HDDS-14355. Allow createKey to skip allocateBlock for
empty key (#9626)
eb178061112 is described below
commit eb1780611125d083f6ca0a1047006bcc40e4736b
Author: Ivan Andika <[email protected]>
AuthorDate: Thu Jan 15 17:16:21 2026 +0800
HDDS-14355. Allow createKey to skip allocateBlock for empty key (#9626)
---
.../hadoop/hdds/scm/pipeline/PipelineManager.java | 5 +
.../hdds/scm/pipeline/PipelineManagerImpl.java | 5 +
.../hdds/scm/pipeline/SCMPipelineMetrics.java | 7 +
.../hdds/scm/pipeline/MockPipelineManager.java | 5 +
hadoop-ozone/integration-test-s3/pom.xml | 5 +
.../ozone/s3/awssdk/v1/AbstractS3SDKV1Tests.java | 27 ++-
.../ozone/s3/awssdk/v2/AbstractS3SDKV2Tests.java | 40 +++++
.../ozone/client/rpc/OzoneRpcClientTests.java | 52 ++++++
.../client/rpc/TestBlockDataStreamOutput.java | 4 +-
.../org/apache/hadoop/ozone/om/TestOmMetrics.java | 3 +
.../ozone/om/request/file/OMFileCreateRequest.java | 7 +-
.../ozone/om/request/key/OMKeyCreateRequest.java | 23 ++-
.../om/request/file/TestOMFileCreateRequest.java | 124 +++++++++++++
.../om/request/key/TestOMKeyCreateRequest.java | 196 ++++++++++++++++++---
14 files changed, 468 insertions(+), 35 deletions(-)
diff --git
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/pipeline/PipelineManager.java
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/pipeline/PipelineManager.java
index b4fe1559fd4..6a448d6c88d 100644
---
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/pipeline/PipelineManager.java
+++
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/pipeline/PipelineManager.java
@@ -222,4 +222,9 @@ void reinitialize(Table<PipelineID, Pipeline> pipelineStore)
boolean hasEnoughSpace(Pipeline pipeline, long containerSize);
int openContainerLimit(List<DatanodeDetails> datanodes);
+
+ /**
+ * Get the pipeline metrics.
+ */
+ SCMPipelineMetrics getMetrics();
}
diff --git
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/pipeline/PipelineManagerImpl.java
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/pipeline/PipelineManagerImpl.java
index f53275c65b5..9c529e22e7e 100644
---
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/pipeline/PipelineManagerImpl.java
+++
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/pipeline/PipelineManagerImpl.java
@@ -944,4 +944,9 @@ public void acquireWriteLock() {
public void releaseWriteLock() {
lock.writeLock().unlock();
}
+
+ @Override
+ public SCMPipelineMetrics getMetrics() {
+ return metrics;
+ }
}
diff --git
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/pipeline/SCMPipelineMetrics.java
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/pipeline/SCMPipelineMetrics.java
index 3ba1df1a806..187d4be74b6 100644
---
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/pipeline/SCMPipelineMetrics.java
+++
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/pipeline/SCMPipelineMetrics.java
@@ -189,4 +189,11 @@ void incNumPipelineContainSameDatanodes() {
public void updatePipelineCreationLatencyNs(long startNanos) {
pipelineCreationLatencyNs.add(Time.monotonicNowNanos() - startNanos);
}
+
+ /**
+ * Return number of blocks allocated across all pipelines.
+ */
+ public long getTotalNumBlocksAllocated() {
+ return
numBlocksAllocated.values().stream().mapToLong(MutableCounterLong::value).sum();
+ }
}
diff --git
a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/pipeline/MockPipelineManager.java
b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/pipeline/MockPipelineManager.java
index 04a1dc1bd4e..d6a3fc54635 100644
---
a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/pipeline/MockPipelineManager.java
+++
b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/pipeline/MockPipelineManager.java
@@ -341,4 +341,9 @@ public int openContainerLimit(List<DatanodeDetails>
datanodes) {
// For tests that do not care about this limit, return a large value.
return Integer.MAX_VALUE;
}
+
+ @Override
+ public SCMPipelineMetrics getMetrics() {
+ return null;
+ }
}
diff --git a/hadoop-ozone/integration-test-s3/pom.xml
b/hadoop-ozone/integration-test-s3/pom.xml
index 46b3cbc6513..5d651dec31f 100644
--- a/hadoop-ozone/integration-test-s3/pom.xml
+++ b/hadoop-ozone/integration-test-s3/pom.xml
@@ -86,6 +86,11 @@
<artifactId>hdds-server-framework</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.ozone</groupId>
+ <artifactId>hdds-server-scm</artifactId>
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>org.apache.ozone</groupId>
<artifactId>hdds-test-utils</artifactId>
diff --git
a/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v1/AbstractS3SDKV1Tests.java
b/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v1/AbstractS3SDKV1Tests.java
index c872d19f527..af890ff17e8 100644
---
a/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v1/AbstractS3SDKV1Tests.java
+++
b/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v1/AbstractS3SDKV1Tests.java
@@ -25,6 +25,7 @@
import static org.assertj.core.api.Assertions.assertThat;
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.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -109,6 +110,7 @@
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.OzoneKeyDetails;
import org.apache.hadoop.ozone.client.OzoneVolume;
import org.apache.hadoop.ozone.client.io.OzoneOutputStream;
import org.apache.hadoop.ozone.om.helpers.BucketLayout;
@@ -400,16 +402,39 @@ public void testPutDoubleSlashPrefixObject() throws
IOException {
}
@Test
- public void testPutObjectEmpty() {
+ public void testPutObjectEmpty() throws Exception {
final String bucketName = getBucketName();
final String keyName = getKeyName();
final String content = "";
s3Client.createBucket(bucketName);
+ long initialAllocatedBlocks =
+ cluster.getStorageContainerManager().getPipelineManager()
+ .getMetrics().getTotalNumBlocksAllocated();
+
InputStream is = new
ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8));
PutObjectResult putObjectResult = s3Client.putObject(bucketName, keyName,
is, new ObjectMetadata());
assertEquals("d41d8cd98f00b204e9800998ecf8427e",
putObjectResult.getETag());
+
+ // Verify via Ozone client that the key has no block locations
+ try (OzoneClient ozoneClient = cluster.newClient()) {
+ ObjectStore store = ozoneClient.getObjectStore();
+ OzoneVolume volume = store.getS3Volume();
+ OzoneBucket bucket = volume.getBucket(bucketName);
+
+ OzoneKeyDetails keyDetails = bucket.getKey(keyName);
+ assertNotNull(keyDetails);
+ assertEquals(0, keyDetails.getDataSize(),
+ "Empty S3 object should have dataSize of 0");
+ assertTrue(keyDetails.getOzoneKeyLocations().isEmpty(),
+ "Empty S3 object should have no block locations");
+ }
+
+ // createKey should skip block allocation if data size is 0
+ long currentAllocatedBlocks =
+
cluster.getStorageContainerManager().getPipelineManager().getMetrics().getTotalNumBlocksAllocated();
+ assertEquals(initialAllocatedBlocks, currentAllocatedBlocks);
}
@Test
diff --git
a/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v2/AbstractS3SDKV2Tests.java
b/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v2/AbstractS3SDKV2Tests.java
index 73dac51346d..5a46afc435c 100644
---
a/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v2/AbstractS3SDKV2Tests.java
+++
b/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v2/AbstractS3SDKV2Tests.java
@@ -25,6 +25,7 @@
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
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 software.amazon.awssdk.core.sync.RequestBody.fromString;
@@ -62,6 +63,7 @@
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.OzoneKeyDetails;
import org.apache.hadoop.ozone.client.OzoneVolume;
import org.apache.hadoop.ozone.client.io.OzoneOutputStream;
import org.apache.hadoop.ozone.s3.S3ClientFactory;
@@ -233,6 +235,44 @@ public void testPutObject() {
assertEquals("\"37b51d194a7513e45b56f6524f2d51f2\"",
getObjectResponse.eTag());
}
+ @Test
+ public void testPutObjectEmpty() throws Exception {
+ final String bucketName = getBucketName();
+ final String keyName = getKeyName();
+ final String content = "";
+ s3Client.createBucket(b -> b.bucket(bucketName));
+
+ long initialAllocatedBlocks =
+ cluster.getStorageContainerManager().getPipelineManager()
+ .getMetrics().getTotalNumBlocksAllocated();
+
+ PutObjectResponse putObjectResponse = s3Client.putObject(b -> b
+ .bucket(bucketName)
+ .key(keyName),
+ RequestBody.fromString(content));
+
+ assertEquals("\"d41d8cd98f00b204e9800998ecf8427e\"",
putObjectResponse.eTag());
+
+ // Verify via Ozone client that the key has no block locations
+ try (OzoneClient ozoneClient = cluster.newClient()) {
+ ObjectStore store = ozoneClient.getObjectStore();
+ OzoneVolume volume = store.getS3Volume();
+ OzoneBucket bucket = volume.getBucket(bucketName);
+
+ OzoneKeyDetails keyDetails = bucket.getKey(keyName);
+ assertNotNull(keyDetails);
+ assertEquals(0, keyDetails.getDataSize(),
+ "Empty S3 object should have dataSize of 0");
+ assertTrue(keyDetails.getOzoneKeyLocations().isEmpty(),
+ "Empty S3 object should have no block locations");
+ }
+
+ // createKey should skip block allocation if data size is 0
+ long currentAllocatedBlocks =
+
cluster.getStorageContainerManager().getPipelineManager().getMetrics().getTotalNumBlocksAllocated();
+ assertEquals(initialAllocatedBlocks, currentAllocatedBlocks);
+ }
+
@Test
public void testListObjectsMany() throws Exception {
testListObjectsMany(false);
diff --git
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/OzoneRpcClientTests.java
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/OzoneRpcClientTests.java
index 3bc30fa0c42..dedc67fece3 100644
---
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/OzoneRpcClientTests.java
+++
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/OzoneRpcClientTests.java
@@ -5271,4 +5271,56 @@ public void testGetObjectTagging(BucketLayout
bucketLayout) throws Exception {
assertEquals(tags.size(), tagsRetrieved.size());
assertThat(tagsRetrieved).containsAllEntriesOf(tags);
}
+
+ @Test
+ public void testCreateEmptyKeySkipBlockAllocation()
+ throws Exception {
+ String volumeName = UUID.randomUUID().toString();
+ String bucketName = UUID.randomUUID().toString();
+ String keyName = UUID.randomUUID().toString();
+
+ getStore().createVolume(volumeName);
+ OzoneVolume volume = getStore().getVolume(volumeName);
+ volume.createBucket(bucketName);
+ OzoneBucket bucket = volume.getBucket(bucketName);
+
+ long initialAllocatedBlocks =
+ getCluster().getStorageContainerManager().getPipelineManager()
+ .getMetrics().getTotalNumBlocksAllocated();
+ // Don't write anything - this is an empty file
+ OzoneOutputStream out = bucket.createKey(keyName, 0);
+ out.close();
+
+ // createKey should skip block allocation if data size is 0
+ long currentAllocatedBlocks =
+
getCluster().getStorageContainerManager().getPipelineManager().getMetrics().getTotalNumBlocksAllocated();
+ assertEquals(initialAllocatedBlocks, currentAllocatedBlocks);
+ }
+
+ @Test
+ public void testCreateEmptyFileNotSkipBlockAllocation()
+ throws Exception {
+ String volumeName = UUID.randomUUID().toString();
+ String bucketName = UUID.randomUUID().toString();
+ String keyName = UUID.randomUUID().toString();
+
+ getStore().createVolume(volumeName);
+ OzoneVolume volume = getStore().getVolume(volumeName);
+ volume.createBucket(bucketName);
+ OzoneBucket bucket = volume.getBucket(bucketName);
+
+ long initialAllocatedBlocks =
+
getCluster().getStorageContainerManager().getPipelineManager().getMetrics().getTotalNumBlocksAllocated();
+ // Don't write anything - this is an empty file
+ OzoneOutputStream out = bucket.createFile(keyName, 0,
+ RatisReplicationConfig.getInstance(HddsProtos.ReplicationFactor.ONE),
+ false,
+ false);
+ out.close();
+
+ // OM should call allocateBlock in OMFileCreateRequest regardless of data
size
+ long currentAllocatedBlocks =
+
getCluster().getStorageContainerManager().getPipelineManager().getMetrics().getTotalNumBlocksAllocated();
+ assertEquals(initialAllocatedBlocks + 1, currentAllocatedBlocks);
+ }
}
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 3fa28a0da36..3cea0590d85 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
@@ -318,7 +318,7 @@ public void testTotalAckDataLength(boolean flushDelay)
throws Exception {
int dataLength = 400;
String keyName = getKeyName();
OzoneDataStreamOutput key = createKey(
- client, keyName, 0);
+ client, keyName, dataLength);
byte[] data =
ContainerTestHelper.getFixedLengthString(keyString, dataLength)
.getBytes(UTF_8);
@@ -345,7 +345,7 @@ public void testDatanodeVersion(boolean flushDelay) throws
Exception {
}
String keyName = getKeyName();
- OzoneDataStreamOutput key = createKey(client, keyName, 0);
+ OzoneDataStreamOutput key = createKey(client, keyName, 1);
KeyDataStreamOutput keyDataStreamOutput = (KeyDataStreamOutput)
key.getByteBufStreamOutput();
BlockDataStreamOutputEntry stream =
keyDataStreamOutput.getStreamEntries().get(0);
diff --git
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOmMetrics.java
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOmMetrics.java
index 495ff705607..9d3aedd4edc 100644
---
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOmMetrics.java
+++
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOmMetrics.java
@@ -966,9 +966,11 @@ private void doKeyOps(OmKeyArgs keyArgs) {
private OmKeyArgs createKeyArgs(String volumeName, String bucketName,
ReplicationConfig repConfig) throws IOException {
+ int dataLength = 10;
OmKeyLocationInfo keyLocationInfo = new OmKeyLocationInfo.Builder()
.setBlockID(new BlockID(new ContainerBlockID(1, 1)))
.setPipeline(MockPipeline.createSingleNodePipeline())
+ .setLength(dataLength)
.build();
keyLocationInfo.setCreateVersion(0);
@@ -980,6 +982,7 @@ private OmKeyArgs createKeyArgs(String volumeName, String
bucketName,
.setKeyName(keyName)
.setAcls(Lists.emptyList())
.setReplicationConfig(repConfig)
+ .setDataSize(dataLength)
.setOwnerName(UserGroupInformation.getCurrentUser().getShortUserName())
.build();
}
diff --git
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/file/OMFileCreateRequest.java
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/file/OMFileCreateRequest.java
index ecb86300835..9788cfbafe1 100644
---
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/file/OMFileCreateRequest.java
+++
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/file/OMFileCreateRequest.java
@@ -123,7 +123,12 @@ public OMRequest preExecute(OzoneManager ozoneManager)
throws IOException {
// TODO: Here we are allocating block with out any check for
// bucket/key/volume or not and also with out any authorization checks.
-
+ // We also allocate block even if requestedSize is 0 because unlike
+ // CreateKey which is used for S3 use case where the requested dataSize is
known in advance
+ // and KeyArgs.dataSize is not going to be set for empty key
+ // File system client does not know the final file size in advance but use
0 as
+ // the placeholder for the data size. Therefore, we should at least
allocate a
+ // single block and we cannot simply skip the allocate block call
List< OmKeyLocationInfo > omKeyLocationInfoList =
allocateBlock(ozoneManager.getScmClient(),
ozoneManager.getBlockTokenSecretManager(), repConfig,
diff --git
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyCreateRequest.java
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyCreateRequest.java
index 345ac492a04..d34320ecb8d 100644
---
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyCreateRequest.java
+++
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyCreateRequest.java
@@ -26,6 +26,7 @@
import java.nio.file.InvalidPathException;
import java.nio.file.Paths;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -117,6 +118,7 @@ public OMRequest preExecute(OzoneManager ozoneManager)
throws IOException {
CreateKeyRequest.Builder newCreateKeyRequest = null;
KeyArgs.Builder newKeyArgs = null;
+ UserInfo userInfo = getUserInfo();
if (!keyArgs.getIsMultipartKey()) {
long scmBlockSize = ozoneManager.getScmBlockSize();
@@ -143,10 +145,17 @@ public OMRequest preExecute(OzoneManager ozoneManager)
throws IOException {
// bucket/key/volume or not and also with out any authorization checks.
// As for a client for the first time this can be executed on any OM,
// till leader is identified.
- UserInfo userInfo = getUserInfo();
- List<OmKeyLocationInfo> omKeyLocationInfoList =
- captureLatencyNs(perfMetrics.getCreateKeyAllocateBlockLatencyNs(),
- () -> allocateBlock(ozoneManager.getScmClient(),
+
+ List<OmKeyLocationInfo> omKeyLocationInfoList;
+ final long effectiveDataSize;
+ // Skip block allocation if dataSize <= 0. We also consider unspecified
dataSize as
+ // empty key since the client will not set dataSize if the key is empty
(i.e. dataSize <= 0),
+ if (!keyArgs.hasDataSize() || keyArgs.getDataSize() <= 0) {
+ omKeyLocationInfoList = Collections.emptyList();
+ effectiveDataSize = 0;
+ } else {
+ omKeyLocationInfoList =
captureLatencyNs(perfMetrics.getCreateKeyAllocateBlockLatencyNs(),
+ () -> allocateBlock(ozoneManager.getScmClient(),
ozoneManager.getBlockTokenSecretManager(), repConfig,
new ExcludeList(), requestedSize, scmBlockSize,
ozoneManager.getPreallocateBlocksMax(),
@@ -155,10 +164,12 @@ public OMRequest preExecute(OzoneManager ozoneManager)
throws IOException {
ozoneManager.getMetrics(),
keyArgs.getSortDatanodes(),
userInfo));
+ effectiveDataSize = requestedSize;
+ }
newKeyArgs = keyArgs.toBuilder().setModificationTime(Time.now())
.setType(type).setFactor(factor)
- .setDataSize(requestedSize);
+ .setDataSize(effectiveDataSize);
newKeyArgs.addAllKeyLocations(omKeyLocationInfoList.stream()
.map(info -> info.getProtobuf(false,
@@ -186,7 +197,7 @@ public OMRequest preExecute(OzoneManager ozoneManager)
throws IOException {
.setClientID(UniqueId.next());
return getOmRequest().toBuilder()
- .setCreateKeyRequest(newCreateKeyRequest).setUserInfo(getUserInfo())
+ .setCreateKeyRequest(newCreateKeyRequest).setUserInfo(userInfo)
.build();
}
diff --git
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/file/TestOMFileCreateRequest.java
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/file/TestOMFileCreateRequest.java
index a5b792c717e..3004f511480 100644
---
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/file/TestOMFileCreateRequest.java
+++
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/file/TestOMFileCreateRequest.java
@@ -33,7 +33,12 @@
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import jakarta.annotation.Nonnull;
@@ -45,8 +50,14 @@
import org.apache.hadoop.crypto.CipherSuite;
import org.apache.hadoop.crypto.CryptoProtocolVersion;
import org.apache.hadoop.crypto.key.KeyProviderCryptoExtension;
+import org.apache.hadoop.hdds.client.ContainerBlockID;
import org.apache.hadoop.hdds.client.ReplicationConfig;
+import org.apache.hadoop.hdds.client.StandaloneReplicationConfig;
import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.hdds.scm.container.common.helpers.AllocatedBlock;
+import org.apache.hadoop.hdds.scm.container.common.helpers.ExcludeList;
+import org.apache.hadoop.hdds.scm.pipeline.Pipeline;
+import org.apache.hadoop.hdds.scm.pipeline.PipelineID;
import org.apache.hadoop.ozone.OzoneAcl;
import org.apache.hadoop.ozone.om.exceptions.OMException;
import org.apache.hadoop.ozone.om.helpers.BucketEncryptionKeyInfo;
@@ -689,6 +700,119 @@ protected OMRequest createFileRequest(
}
+ /**
+ * Test that SCM's allocateBlock is still called when creating an empty file.
+ */
+ @Test
+ public void testZeroSizedFileShouldCallAllocateBlock() throws Exception {
+ KeyArgs.Builder keyArgs = KeyArgs.newBuilder()
+ .setVolumeName(volumeName).setBucketName(bucketName)
+ .setKeyName(keyName)
+ .setFactor(HddsProtos.ReplicationFactor.ONE)
+ .setType(HddsProtos.ReplicationType.RATIS)
+ .setDataSize(0); // Set data size to 0 for empty file
+
+ CreateFileRequest createFileRequest = CreateFileRequest.newBuilder()
+ .setKeyArgs(keyArgs)
+ .setIsOverwrite(false)
+ .setIsRecursive(true).build();
+
+ OMRequest omRequest = OMRequest.newBuilder()
+ .setCmdType(OzoneManagerProtocolProtos.Type.CreateFile)
+ .setClientId(UUID.randomUUID().toString())
+ .setCreateFileRequest(createFileRequest).build();
+
+ OMFileCreateRequest omFileCreateRequest =
getOMFileCreateRequest(omRequest);
+
+ OMRequest modifiedOmRequest = omFileCreateRequest.preExecute(ozoneManager);
+
+ // Verify that SCM's allocateBlock is always called
+ verify(scmBlockLocationProtocol, atLeastOnce())
+ .allocateBlock(anyLong(), anyInt(),
+ any(ReplicationConfig.class), anyString(),
+ any(ExcludeList.class), anyString());
+
+ // Verify key locations are present in the response
+ assertTrue(modifiedOmRequest.hasCreateFileRequest());
+ CreateFileRequest responseCreateFileRequest =
+ modifiedOmRequest.getCreateFileRequest();
+ assertTrue(
+ responseCreateFileRequest.getKeyArgs().getKeyLocationsCount() > 0,
+ "File with zero dataSize should still have key locations allocated");
+ }
+
+ /**
+ * Test that SCM's allocateBlock is still called when creating a file
+ * without explicitly setting dataSize (should use default scmBlockSize).
+ */
+ @Test
+ public void testFileWithoutDataSizeShouldAllocateBlock() throws Exception {
+ // Setup mock to return valid blocks
+ Pipeline pipeline = Pipeline.newBuilder()
+ .setState(Pipeline.PipelineState.OPEN)
+ .setId(PipelineID.randomId())
+ .setReplicationConfig(
+ StandaloneReplicationConfig.getInstance(
+ HddsProtos.ReplicationFactor.ONE))
+ .setNodes(new ArrayList<>())
+ .build();
+
+ AllocatedBlock.Builder blockBuilder = new AllocatedBlock.Builder()
+ .setPipeline(pipeline)
+ .setContainerBlockID(new ContainerBlockID(CONTAINER_ID, LOCAL_ID));
+
+ when(scmBlockLocationProtocol.allocateBlock(
+ anyLong(), anyInt(),
+ any(ReplicationConfig.class), anyString(),
+ any(ExcludeList.class), anyString()))
+ .thenAnswer(invocation -> {
+ int num = invocation.getArgument(1);
+ List<AllocatedBlock> allocatedBlocks = new ArrayList<>(num);
+ for (int i = 0; i < num; i++) {
+ blockBuilder.setContainerBlockID(
+ new ContainerBlockID(CONTAINER_ID + i, LOCAL_ID + i));
+ allocatedBlocks.add(blockBuilder.build());
+ }
+ return allocatedBlocks;
+ });
+
+ // Create a file request WITHOUT setting dataSize (should default to
scmBlockSize)
+ KeyArgs.Builder keyArgs = KeyArgs.newBuilder()
+ .setVolumeName(volumeName).setBucketName(bucketName)
+ .setKeyName(keyName)
+ .setFactor(HddsProtos.ReplicationFactor.ONE)
+ .setType(HddsProtos.ReplicationType.RATIS);
+ // Note: dataSize is not set
+
+ CreateFileRequest createFileRequest = CreateFileRequest.newBuilder()
+ .setKeyArgs(keyArgs)
+ .setIsOverwrite(false)
+ .setIsRecursive(true).build();
+
+ OMRequest omRequest = OMRequest.newBuilder()
+ .setCmdType(OzoneManagerProtocolProtos.Type.CreateFile)
+ .setClientId(UUID.randomUUID().toString())
+ .setCreateFileRequest(createFileRequest).build();
+
+ OMFileCreateRequest omFileCreateRequest =
getOMFileCreateRequest(omRequest);
+
+ OMRequest modifiedOmRequest = omFileCreateRequest.preExecute(ozoneManager);
+
+ // Verify that SCM's allocateBlock was called
+ verify(scmBlockLocationProtocol, atLeastOnce())
+ .allocateBlock(anyLong(), anyInt(),
+ any(ReplicationConfig.class), anyString(),
+ any(ExcludeList.class), anyString());
+
+ // Verify key locations are present in the response
+ assertTrue(modifiedOmRequest.hasCreateFileRequest());
+ CreateFileRequest responseCreateFileRequest =
+ modifiedOmRequest.getCreateFileRequest();
+ assertTrue(
+ responseCreateFileRequest.getKeyArgs().getKeyLocationsCount() > 0,
+ "File without explicit dataSize should have key locations allocated");
+ }
+
/**
* Gets OMFileCreateRequest reference.
*
diff --git
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeyCreateRequest.java
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeyCreateRequest.java
index b4cf5f7cc14..1666f4cb38e 100644
---
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeyCreateRequest.java
+++
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeyCreateRequest.java
@@ -39,6 +39,12 @@
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.io.IOException;
@@ -52,11 +58,17 @@
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
+import org.apache.hadoop.hdds.client.ContainerBlockID;
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.client.StandaloneReplicationConfig;
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
import org.apache.hadoop.hdds.protocol.proto.HddsProtos.KeyValue;
+import org.apache.hadoop.hdds.scm.container.common.helpers.AllocatedBlock;
+import org.apache.hadoop.hdds.scm.container.common.helpers.ExcludeList;
+import org.apache.hadoop.hdds.scm.pipeline.Pipeline;
+import org.apache.hadoop.hdds.scm.pipeline.PipelineID;
import org.apache.hadoop.ozone.OzoneAcl;
import org.apache.hadoop.ozone.OzoneConsts;
import org.apache.hadoop.ozone.om.OmConfig;
@@ -275,13 +287,27 @@ private void checkResponse(
assertNotNull(omKeyInfo);
assertNotNull(omKeyInfo.getLatestVersionLocations());
- // As our data size is 100, and scmBlockSize is default to 1000, so we
- // shall have only one block.
List<OmKeyLocationInfo> omKeyLocationInfoList =
omKeyInfo.getLatestVersionLocations().getLocationList();
- assertEquals(1, omKeyLocationInfoList.size());
+ if (modifiedOmRequest.getCreateKeyRequest().getKeyArgs().getDataSize() >
0) {
+ // As our data size is 100, and scmBlockSize is default to 1000, so we
+ // shall have only one block.
+ assertEquals(1, omKeyLocationInfoList.size());
+
+ OmKeyLocationInfo omKeyLocationInfo = omKeyLocationInfoList.get(0);
+ // Check data of the block
+ OzoneManagerProtocolProtos.KeyLocation keyLocation =
+
modifiedOmRequest.getCreateKeyRequest().getKeyArgs().getKeyLocations(0);
+
+ assertEquals(keyLocation.getBlockID().getContainerBlockID()
+ .getContainerID(), omKeyLocationInfo.getContainerID());
+ assertEquals(keyLocation.getBlockID().getContainerBlockID()
+ .getLocalID(), omKeyLocationInfo.getLocalID());
+ } else {
+ // When creating an empty key, there should not be any blocks
+ assertEquals(0, omKeyLocationInfoList.size());
+ }
- OmKeyLocationInfo omKeyLocationInfo = omKeyLocationInfoList.get(0);
// Check modification time
assertEquals(modifiedOmRequest.getCreateKeyRequest()
@@ -294,15 +320,6 @@ private void checkResponse(
assertNotEquals(omKeyInfo.getModificationTime(),
omKeyInfo.getCreationTime());
}
-
- // Check data of the block
- OzoneManagerProtocolProtos.KeyLocation keyLocation =
-
modifiedOmRequest.getCreateKeyRequest().getKeyArgs().getKeyLocations(0);
-
- assertEquals(keyLocation.getBlockID().getContainerBlockID()
- .getContainerID(), omKeyLocationInfo.getContainerID());
- assertEquals(keyLocation.getBlockID().getContainerBlockID()
- .getLocalID(), omKeyLocationInfo.getLocalID());
}
@ParameterizedTest
@@ -633,7 +650,8 @@ private OMRequest doPreExecute(OMRequest originalOMRequest)
throws Exception {
keyArgs.getEcReplicationConfig().getData() : 1;
long blockSize = ozoneManager.getScmBlockSize();
long preAllocatedBlocks = Math.min(ozoneManager.getPreallocateBlocksMax(),
- (keyArgs.getDataSize() - 1) / (blockSize * dataGroupSize) + 1);
+ keyArgs.getDataSize() > 0 ?
+ (keyArgs.getDataSize() - 1) / (blockSize * dataGroupSize) + 1 : 0);
// Time should be set
assertThat(keyArgs.getModificationTime()).isGreaterThan(0);
@@ -651,17 +669,19 @@ private OMRequest doPreExecute(OMRequest
originalOMRequest) throws Exception {
keyArgs.getKeyLocationsList();
// KeyLocation should be set.
assertEquals(preAllocatedBlocks, keyLocations.size());
- assertEquals(CONTAINER_ID,
- keyLocations.get(0).getBlockID().getContainerBlockID()
- .getContainerID());
- assertEquals(LOCAL_ID,
- keyLocations.get(0).getBlockID().getContainerBlockID()
- .getLocalID());
- assertTrue(keyLocations.get(0).hasPipeline());
-
- assertEquals(0, keyLocations.get(0).getOffset());
-
- assertEquals(scmBlockSize, keyLocations.get(0).getLength());
+ if (preAllocatedBlocks > 0) {
+ assertEquals(CONTAINER_ID,
+ keyLocations.get(0).getBlockID().getContainerBlockID()
+ .getContainerID());
+ assertEquals(LOCAL_ID,
+ keyLocations.get(0).getBlockID().getContainerBlockID()
+ .getLocalID());
+ assertTrue(keyLocations.get(0).hasPipeline());
+
+ assertEquals(0, keyLocations.get(0).getOffset());
+
+ assertEquals(scmBlockSize, keyLocations.get(0).getLength());
+ }
} else {
// We don't create blocks for multipart key in createKey preExecute.
assertEquals(0, keyArgs.getKeyLocationsList().size());
@@ -1070,6 +1090,132 @@ private void verifyKeyInheritAcls(List<OzoneAcl>
keyAcls,
assertThat(keyAcls).doesNotContain(parentAccessAcl);
}
+ /**
+ * Test that SCM's allocateBlock is not called when creating an empty key.
+ */
+ @Test
+ public void testEmptyKeyKeyDoesNotCallScmAllocateBlock() throws Exception {
+ KeyArgs.Builder keyArgs = KeyArgs.newBuilder()
+ .setVolumeName(volumeName).setBucketName(bucketName)
+ .setKeyName(keyName).setIsMultipartKey(false)
+ .setFactor(
+ ((RatisReplicationConfig)
replicationConfig).getReplicationFactor())
+ .setType(replicationConfig.getReplicationType())
+ .setLatestVersionLocation(true)
+ .setDataSize(0); // explicitly set data size to 0
+
+ CreateKeyRequest createKeyRequest =
+ CreateKeyRequest.newBuilder().setKeyArgs(keyArgs).build();
+
+ OMRequest omRequest = OMRequest.newBuilder()
+ .setCmdType(OzoneManagerProtocolProtos.Type.CreateKey)
+ .setClientId(UUID.randomUUID().toString())
+ .setCreateKeyRequest(createKeyRequest).build();
+
+ OMKeyCreateRequest omKeyCreateRequest = getOMKeyCreateRequest(omRequest);
+
+ OMRequest modifiedOmRequest = omKeyCreateRequest.preExecute(ozoneManager);
+
+ // Verify that SCM's allocateBlock was never called
+ verify(scmBlockLocationProtocol, never())
+ .allocateBlock(anyLong(), anyInt(),
+ any(ReplicationConfig.class), anyString(),
+ any(ExcludeList.class), anyString());
+
+ verify(scmBlockLocationProtocol, never())
+ .allocateBlock(anyLong(), anyInt(),
+ any(ReplicationConfig.class), anyString(),
+ any(ExcludeList.class), anyString());
+
+ assertTrue(modifiedOmRequest.hasCreateKeyRequest());
+ CreateKeyRequest responseCreateKeyRequest =
+ modifiedOmRequest.getCreateKeyRequest();
+ assertEquals(0,
+ responseCreateKeyRequest.getKeyArgs().getKeyLocationsCount(),
+ "Empty key should have no key locations");
+
+ assertEquals(0,
+ responseCreateKeyRequest.getKeyArgs().getDataSize(),
+ "Empty key should have dataSize of 0");
+ }
+
+ /**
+ * Test that SCM's allocateBlock is not called when creating a key without
specifying the dataSize.
+ */
+ @Test
+ public void testKeyWithoutDataSizeCallsScmAllocateBlock() throws Exception {
+ // Setup mock to return valid blocks
+ Pipeline pipeline = Pipeline.newBuilder()
+ .setState(Pipeline.PipelineState.OPEN)
+ .setId(PipelineID.randomId())
+ .setReplicationConfig(
+ StandaloneReplicationConfig.getInstance(
+
org.apache.hadoop.hdds.protocol.proto.HddsProtos.ReplicationFactor.ONE))
+ .setNodes(new ArrayList<>())
+ .build();
+
+ AllocatedBlock.Builder blockBuilder = new AllocatedBlock.Builder()
+ .setPipeline(pipeline)
+ .setContainerBlockID(new ContainerBlockID(CONTAINER_ID, LOCAL_ID));
+
+ when(scmBlockLocationProtocol.allocateBlock(
+ anyLong(), anyInt(),
+ any(ReplicationConfig.class), anyString(),
+ any(ExcludeList.class), anyString()))
+ .thenAnswer(invocation -> {
+ int num = invocation.getArgument(1);
+ List<AllocatedBlock> allocatedBlocks = new ArrayList<>(num);
+ for (int i = 0; i < num; i++) {
+ blockBuilder.setContainerBlockID(
+ new ContainerBlockID(CONTAINER_ID + i, LOCAL_ID + i));
+ allocatedBlocks.add(blockBuilder.build());
+ }
+ return allocatedBlocks;
+ });
+
+ // Create a key request without setting dataSize (should default to
scmBlockSize)
+ KeyArgs.Builder keyArgs = KeyArgs.newBuilder()
+ .setVolumeName(volumeName).setBucketName(bucketName)
+ .setKeyName(keyName).setIsMultipartKey(false)
+ .setFactor(
+ ((RatisReplicationConfig)
replicationConfig).getReplicationFactor())
+ .setType(replicationConfig.getReplicationType())
+ .setLatestVersionLocation(true);
+
+ CreateKeyRequest createKeyRequest =
+ CreateKeyRequest.newBuilder().setKeyArgs(keyArgs).build();
+
+ OMRequest omRequest = OMRequest.newBuilder()
+ .setCmdType(OzoneManagerProtocolProtos.Type.CreateKey)
+ .setClientId(UUID.randomUUID().toString())
+ .setCreateKeyRequest(createKeyRequest).build();
+
+ OMKeyCreateRequest omKeyCreateRequest = getOMKeyCreateRequest(omRequest);
+
+ OMRequest modifiedOmRequest = omKeyCreateRequest.preExecute(ozoneManager);
+
+ verify(scmBlockLocationProtocol, never())
+ .allocateBlock(anyLong(), anyInt(),
+ any(ReplicationConfig.class), anyString(),
+ any(ExcludeList.class), anyString());
+
+ verify(scmBlockLocationProtocol, never())
+ .allocateBlock(anyLong(), anyInt(),
+ any(ReplicationConfig.class), anyString(),
+ any(ExcludeList.class), anyString());
+
+ assertTrue(modifiedOmRequest.hasCreateKeyRequest());
+ CreateKeyRequest responseCreateKeyRequest =
+ modifiedOmRequest.getCreateKeyRequest();
+ assertEquals(0,
+ responseCreateKeyRequest.getKeyArgs().getKeyLocationsCount(),
+ "Empty key should have no key locations");
+
+ assertEquals(0,
+ responseCreateKeyRequest.getKeyArgs().getDataSize(),
+ "Empty key should have dataSize of 0");
+ }
+
protected void addToKeyTable(String keyName) throws Exception {
OMRequestTestUtils.addKeyToTable(false, volumeName, bucketName,
keyName.substring(1), 0L, RatisReplicationConfig.getInstance(THREE),
omMetadataManager);
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]