You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ozone.apache.org by ad...@apache.org on 2026/09/26 06:55:31 UTC
(ozone) branch master updated: HDDS-16604. S3 DeleteObjects returns 200 when OM rejects the whole batch (#11327)
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 ad059968a55 HDDS-16604. S3 DeleteObjects returns 200 when OM rejects the whole batch (#11327)
ad059968a55 is described below
commit ad059968a55717eef00607ca2da9fe95926edac7
Author: Mark Tsai <ma...@gmail.com>
AuthorDate: Sat Sep 26 14:55:24 2026 +0800
HDDS-16604. S3 DeleteObjects returns 200 when OM rejects the whole batch (#11327)
---
.../apache/hadoop/ozone/client/OzoneBucket.java | 5 +-
.../ozone/client/protocol/ClientProtocol.java | 5 +-
.../ozone/om/protocol/OzoneManagerProtocol.java | 5 +-
...OzoneManagerProtocolClientSideTranslatorPB.java | 5 +-
...OzoneManagerProtocolClientSideTranslatorPB.java | 63 ++++++++++++++++++++++
5 files changed, 76 insertions(+), 7 deletions(-)
diff --git a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/OzoneBucket.java b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/OzoneBucket.java
index e3cbcce76f3..29fa6de0ff6 100644
--- a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/OzoneBucket.java
+++ b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/OzoneBucket.java
@@ -862,8 +862,9 @@ public void deleteKeys(List<String> keyList) throws IOException {
/**
* Deletes the given list of keys from the bucket.
* @param keyList List of the key name to be deleted.
- * @param quiet flag to not throw exception if delete fails
- * @throws IOException
+ * @param quiet if true, per-key failures are returned in the result map instead of being thrown
+ * @return key name to error for each key that could not be deleted, empty if all keys were deleted
+ * @throws IOException if the request fails as a whole (e.g. bucket not found), even when quiet is true
*/
public Map<String, ErrorInfo> deleteKeys(List<String> keyList, boolean quiet) throws IOException {
return proxy.deleteKeys(volumeName, name, keyList, quiet);
diff --git a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/protocol/ClientProtocol.java b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/protocol/ClientProtocol.java
index c4355581db6..cb553d33a9d 100644
--- a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/protocol/ClientProtocol.java
+++ b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/protocol/ClientProtocol.java
@@ -680,8 +680,9 @@ void deleteKeys(String volumeName, String bucketName,
* @param volumeName Name of the Volume
* @param bucketName Name of the Bucket
* @param keyNameList List of the Key
- * @param quiet flag to not throw exception if delete fails
- * @throws IOException
+ * @param quiet if true, per-key failures are returned in the result map instead of being thrown
+ * @return key name to error for each key that could not be deleted, empty if all keys were deleted
+ * @throws IOException if the request fails as a whole (e.g. bucket not found), even when quiet is true
*/
Map<String, ErrorInfo> deleteKeys(String volumeName, String bucketName,
List<String> keyNameList, boolean quiet)
diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerProtocol.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerProtocol.java
index 3eea12d53f8..71c2d6b2294 100644
--- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerProtocol.java
+++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerProtocol.java
@@ -379,8 +379,9 @@ default void deleteKeys(OmDeleteKeys deleteKeys) throws IOException {
* through OzoneFileSystem.
*
* @param deleteKeys
- * @param quiet - flag to not throw exception if delete fails
- * @throws IOException
+ * @param quiet - if true, per-key failures are returned in the result map instead of being thrown
+ * @return key name to error for each key that could not be deleted, empty if all keys were deleted
+ * @throws IOException if the request fails as a whole (e.g. bucket not found), even when quiet is true
*/
default Map<String, ErrorInfo> deleteKeys(OmDeleteKeys deleteKeys, boolean quiet)
throws IOException {
diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolClientSideTranslatorPB.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolClientSideTranslatorPB.java
index 79f2d3900c2..a6f8c7053c1 100644
--- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolClientSideTranslatorPB.java
+++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolClientSideTranslatorPB.java
@@ -24,6 +24,7 @@
import static org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.Status.ACCESS_DENIED;
import static org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.Status.DIRECTORY_ALREADY_EXISTS;
import static org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.Status.OK;
+import static org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.Status.PARTIAL_DELETE;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
@@ -1041,7 +1042,9 @@ public Map<String, ErrorInfo> deleteKeys(OmDeleteKeys deleteKeys, boolean quiet)
OMResponse omResponse = submitRequest(omRequest);
Map<String, ErrorInfo> keyToErrors = new HashMap<>();
- if (quiet) {
+ if (quiet && omResponse.getStatus() == PARTIAL_DELETE) {
+ // PARTIAL_DELETE means the batch was processed and only some keys failed; those are reported per key
+ // in the returned map. Any other non-OK status means the whole request failed.
List<OzoneManagerProtocolProtos.DeleteKeyError> errors =
omResponse.getDeleteKeysResponse().getErrorsList();
for (OzoneManagerProtocolProtos.DeleteKeyError deleteKeyError : errors) {
diff --git a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/protocolPB/TestOzoneManagerProtocolClientSideTranslatorPB.java b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/protocolPB/TestOzoneManagerProtocolClientSideTranslatorPB.java
index e08d1e35df7..e09e196f6f3 100644
--- a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/protocolPB/TestOzoneManagerProtocolClientSideTranslatorPB.java
+++ b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/protocolPB/TestOzoneManagerProtocolClientSideTranslatorPB.java
@@ -19,6 +19,7 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -26,8 +27,16 @@
import static org.mockito.Mockito.when;
import java.io.IOException;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
+import org.apache.hadoop.ozone.om.exceptions.OMException;
+import org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes;
+import org.apache.hadoop.ozone.om.helpers.ErrorInfo;
+import org.apache.hadoop.ozone.om.helpers.OmDeleteKeys;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.DeleteKeyError;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.DeleteKeysResponse;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMResponse;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.StartQuotaRepairRequest;
@@ -42,6 +51,7 @@ class TestOzoneManagerProtocolClientSideTranslatorPB {
private final OmTransport omTransport = mock(OmTransport.class);
private final OzoneManagerProtocolClientSideTranslatorPB pb = new OzoneManagerProtocolClientSideTranslatorPB(
omTransport, "test-client-id");
+ private static final OmDeleteKeys DELETE_KEYS = new OmDeleteKeys("vol", "bucket", Arrays.asList("key1", "key2"));
@Test
void testStartQuotaRepair() throws IOException {
@@ -99,4 +109,57 @@ void testStartQuotaRepairWithNullBuckets() {
verifyNoInteractions(omTransport);
}
+ @Test
+ void testQuietDeleteKeysThrowsWhenWholeBatchFails() throws IOException {
+ // Whole-batch failure at OM: non-OK status, DeleteKeysResponse has status=false and no per-key errors.
+ when(omTransport.submitRequest(any(OMRequest.class))).thenReturn(
+ OMResponse.newBuilder()
+ .setCmdType(Type.DeleteKeys)
+ .setStatus(Status.BUCKET_NOT_FOUND)
+ .setSuccess(false)
+ .setMessage("Bucket not found")
+ .setDeleteKeysResponse(DeleteKeysResponse.newBuilder().setStatus(false))
+ .build());
+
+ OMException e = assertThrows(OMException.class, () -> pb.deleteKeys(DELETE_KEYS, true));
+
+ assertThat(e.getResult()).isEqualTo(ResultCodes.BUCKET_NOT_FOUND);
+ assertThat(e).hasMessage("Bucket not found");
+ }
+
+ @Test
+ void testQuietDeleteKeysReturnsPerKeyErrorsOnPartialDelete() throws IOException {
+ DeleteKeyError keyError = DeleteKeyError.newBuilder()
+ .setKey("key2")
+ .setErrorCode(ResultCodes.ACCESS_DENIED.name())
+ .setErrorMsg("ACL check failed")
+ .build();
+ when(omTransport.submitRequest(any(OMRequest.class))).thenReturn(
+ OMResponse.newBuilder()
+ .setCmdType(Type.DeleteKeys)
+ .setStatus(Status.PARTIAL_DELETE)
+ .setSuccess(false)
+ .setDeleteKeysResponse(DeleteKeysResponse.newBuilder().setStatus(false).addErrors(keyError))
+ .build());
+
+ Map<String, ErrorInfo> errors = pb.deleteKeys(DELETE_KEYS, true);
+
+ assertThat(errors).hasSize(1).containsKey("key2");
+ assertThat(errors.get("key2").getCode()).isEqualTo(ResultCodes.ACCESS_DENIED.name());
+ assertThat(errors.get("key2").getMessage()).isEqualTo("ACL check failed");
+ }
+
+ @Test
+ void testQuietDeleteKeysReturnsEmptyMapOnSuccess() throws IOException {
+ when(omTransport.submitRequest(any(OMRequest.class))).thenReturn(
+ OMResponse.newBuilder()
+ .setCmdType(Type.DeleteKeys)
+ .setStatus(Status.OK)
+ .setSuccess(true)
+ .setDeleteKeysResponse(DeleteKeysResponse.newBuilder().setStatus(true))
+ .build());
+
+ assertThat(pb.deleteKeys(DELETE_KEYS, true)).isEmpty();
+ }
+
}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@ozone.apache.org
For additional commands, e-mail: commits-help@ozone.apache.org