From f11df1ecd3bdb82e459bd309521ef725de222e29 Mon Sep 17 00:00:00 2001 From: "Doroszlai, Attila" Date: Sat, 18 Apr 2026 09:14:33 +0200 Subject: [PATCH 1/9] move translateException to S3ErrorTable, add more cases --- .../ozone/s3/endpoint/BucketEndpoint.java | 29 ++--------- .../ozone/s3/exception/S3ErrorTable.java | 49 +++++++++++++++++++ 2 files changed, 53 insertions(+), 25 deletions(-) diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketEndpoint.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketEndpoint.java index c11e732b6bb..2fd6dee16db 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketEndpoint.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketEndpoint.java @@ -23,7 +23,6 @@ import static org.apache.hadoop.ozone.s3.S3GatewayConfigKeys.OZONE_S3G_LIST_KEYS_SHALLOW_ENABLED_DEFAULT; import static org.apache.hadoop.ozone.s3.S3GatewayConfigKeys.OZONE_S3G_LIST_MAX_KEYS_LIMIT; import static org.apache.hadoop.ozone.s3.S3GatewayConfigKeys.OZONE_S3G_LIST_MAX_KEYS_LIMIT_DEFAULT; -import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.INTERNAL_ERROR; import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.newError; import static org.apache.hadoop.ozone.s3.util.S3Consts.ENCODING_TYPE; import static org.apache.hadoop.ozone.s3.util.S3Utils.wrapInQuotes; @@ -99,7 +98,7 @@ public Response get( try { return handler.handleGetRequest(context, bucketName); } catch (OMException ex) { - throw newError(translateException(ex), bucketName, ex); + throw newError(bucketName, ex); } } @@ -270,7 +269,7 @@ public Response put( try { return handler.handlePutRequest(context, bucketName, body); } catch (OMException ex) { - throw newError(translateException(ex), bucketName, ex); + throw newError(bucketName, ex); } } @@ -298,7 +297,7 @@ public Response head(@PathParam(BUCKET) String bucketName) return Response.ok().build(); } catch (OMException e) { auditReadFailure(s3GAction, e); - throw newError(translateException(e), bucketName, e); + throw newError(bucketName, e); } catch (Exception e) { auditReadFailure(s3GAction, e); throw e; @@ -318,7 +317,7 @@ public Response delete(@PathParam(BUCKET) String bucketName) try { return handler.handleDeleteRequest(context, bucketName); } catch (OMException ex) { - throw newError(translateException(ex), bucketName, ex); + throw newError(bucketName, ex); } } @@ -427,24 +426,4 @@ protected void init() { .build(); handler = new AuditingBucketOperationHandler(chain); } - - private static S3ErrorTable translateException(OMException ex) { - switch (ex.getResult()) { - case ACCESS_DENIED: - case INVALID_TOKEN: - case PERMISSION_DENIED: - return S3ErrorTable.ACCESS_DENIED; - case BUCKET_ALREADY_EXISTS: - return S3ErrorTable.BUCKET_ALREADY_EXISTS; - case BUCKET_NOT_EMPTY: - return S3ErrorTable.BUCKET_NOT_EMPTY; - case BUCKET_NOT_FOUND: - case VOLUME_NOT_FOUND: - return S3ErrorTable.NO_SUCH_BUCKET; - case INVALID_BUCKET_NAME: - return S3ErrorTable.INVALID_BUCKET_NAME; - default: - return INTERNAL_ERROR; - } - } } diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java index ef3cef347aa..4b8e7e4d3da 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java @@ -27,6 +27,7 @@ import static org.apache.hadoop.ozone.OzoneConsts.S3_REQUEST_HEADER_METADATA_SIZE_LIMIT_KB; import static org.apache.hadoop.ozone.s3.util.S3Consts.RANGE_NOT_SATISFIABLE; +import org.apache.hadoop.ozone.om.exceptions.OMException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -193,6 +194,54 @@ public int getHttpCode() { return httpCode; } + /** Converts result code of @{code OMException} to {@code S3ErrorTable}, which + * should be thrown via {@link #newError(S3ErrorTable, String, Exception)}. */ + public static S3ErrorTable translateResultCode(OMException ex) { + switch (ex.getResult()) { + case ACCESS_DENIED: + case INVALID_TOKEN: + case PERMISSION_DENIED: + return ACCESS_DENIED; + case ATOMIC_WRITE_CONFLICT: + return CONDITIONAL_REQUEST_CONFLICT; + case BUCKET_ALREADY_EXISTS: + return BUCKET_ALREADY_EXISTS; + case BUCKET_NOT_EMPTY: + return BUCKET_NOT_EMPTY; + case BUCKET_NOT_FOUND: + case VOLUME_NOT_FOUND: + return NO_SUCH_BUCKET; + case ENTITY_TOO_SMALL: + return ENTITY_TOO_SMALL; + case ETAG_MISMATCH: + case ETAG_NOT_AVAILABLE: + case KEY_ALREADY_EXISTS: + return PRECOND_FAILED; + case FILE_ALREADY_EXISTS: + return NO_OVERWRITE; + case INVALID_BUCKET_NAME: + return INVALID_BUCKET_NAME; + case INVALID_PART: + return INVALID_PART; + case INVALID_PART_ORDER: + return INVALID_PART_ORDER; + case INVALID_REQUEST: + return INVALID_REQUEST; + case KEY_NOT_FOUND: + return NO_SUCH_KEY; + case NOT_SUPPORTED_OPERATION: + return NOT_IMPLEMENTED; + case QUOTA_EXCEEDED: + return QUOTA_EXCEEDED; + default: + return INTERNAL_ERROR; + } + } + + public static OS3Exception newError(String resource, OMException e) { + return newError(translateResultCode(e), resource, e); + } + public static OS3Exception newError(S3ErrorTable e, String resource) { return newError(e, resource, null); } From 2dd0d0d7454b215c76f027ed4d51bd828b4a85fd Mon Sep 17 00:00:00 2001 From: "Doroszlai, Attila" Date: Sat, 18 Apr 2026 09:15:47 +0200 Subject: [PATCH 2/9] simplify OS3Exception constructor --- .../ozone/s3/exception/OS3Exception.java | 29 ++++--------------- .../ozone/s3/exception/S3ErrorTable.java | 3 +- 2 files changed, 6 insertions(+), 26 deletions(-) diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/OS3Exception.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/OS3Exception.java index aa97f29e32a..2551fbe0ca1 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/OS3Exception.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/OS3Exception.java @@ -66,31 +66,12 @@ public OS3Exception() { //Added for JaxB. } - /** - * Create an object OS3Exception. - * @param codeVal - * @param messageVal - * @param requestIdVal - * @param resourceVal - */ - public OS3Exception(String codeVal, String messageVal, String requestIdVal, - String resourceVal) { - this.code = codeVal; - this.errorMessage = messageVal; - this.requestId = requestIdVal; - this.resource = resourceVal; - } + OS3Exception(S3ErrorTable error, Exception cause) { + super(error.getErrorMessage(), cause); - /** - * Create an object OS3Exception. - * @param codeVal - * @param messageVal - * @param httpCode - */ - public OS3Exception(String codeVal, String messageVal, int httpCode) { - this.code = codeVal; - this.errorMessage = messageVal; - this.httpCode = httpCode; + code = error.getCode(); + errorMessage = error.getErrorMessage(); + httpCode = error.getHttpCode(); } public String getCode() { diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java index 4b8e7e4d3da..7d385749556 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java @@ -255,8 +255,7 @@ public static OS3Exception newError(S3ErrorTable e, String resource) { */ public static OS3Exception newError(S3ErrorTable e, String resource, Exception ex) { - OS3Exception err = new OS3Exception(e.getCode(), e.getErrorMessage(), - e.getHttpCode()); + OS3Exception err = new OS3Exception(e, ex); err.setResource(resource); if (e.getHttpCode() == HTTP_INTERNAL_ERROR) { LOG.error("Internal Error: {}", err.toXml(), ex); From ff162407f3bf4b7d873a990d6def6f8887fb4b3d Mon Sep 17 00:00:00 2001 From: "Doroszlai, Attila" Date: Sat, 18 Apr 2026 09:28:34 +0200 Subject: [PATCH 3/9] let ObjectEndpoint rely on result code translation in newError --- .../ozone/s3/endpoint/EndpointBase.java | 24 ------ .../s3/endpoint/MultipartKeyHandler.java | 2 - .../ozone/s3/endpoint/ObjectEndpoint.java | 85 +++---------------- .../ozone/s3/exception/S3ErrorTable.java | 5 ++ 4 files changed, 18 insertions(+), 98 deletions(-) diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/EndpointBase.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/EndpointBase.java index edbf2cd4ddc..649b14b49cd 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/EndpointBase.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/EndpointBase.java @@ -221,30 +221,6 @@ protected void init() { // hook method } - protected OzoneBucket getBucket(String bucketName) - throws OS3Exception, IOException { - OzoneBucket bucket; - try { - bucket = client.getObjectStore().getS3Bucket(bucketName); - } catch (OMException ex) { - if (ex.getResult() == ResultCodes.BUCKET_NOT_FOUND - || ex.getResult() == ResultCodes.VOLUME_NOT_FOUND) { - throw newError(S3ErrorTable.NO_SUCH_BUCKET, bucketName, ex); - } else if (ex.getResult() == ResultCodes.INVALID_TOKEN) { - throw newError(S3ErrorTable.ACCESS_DENIED, - s3Auth.getAccessID(), ex); - } else if (ex.getResult() == ResultCodes.PERMISSION_DENIED) { - throw newError(S3ErrorTable.ACCESS_DENIED, bucketName, ex); - } else if (ex.getResult() == ResultCodes.TIMEOUT || - ex.getResult() == ResultCodes.INTERNAL_ERROR) { - throw newError(S3ErrorTable.INTERNAL_ERROR, bucketName, ex); - } else { - throw ex; - } - } - return bucket; - } - protected OzoneVolume getVolume() throws IOException { return client.getObjectStore().getS3Volume(); } diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/MultipartKeyHandler.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/MultipartKeyHandler.java index 69edae42920..984355d10f8 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/MultipartKeyHandler.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/MultipartKeyHandler.java @@ -105,8 +105,6 @@ private Response abortMultipartUpload(OzoneVolume volume, String bucket, } catch (OMException ex) { if (ex.getResult() == ResultCodes.NO_SUCH_MULTIPART_UPLOAD_ERROR) { throw newError(S3ErrorTable.NO_SUCH_UPLOAD, uploadId, ex); - } else if (ex.getResult() == ResultCodes.BUCKET_NOT_FOUND) { - throw newError(S3ErrorTable.NO_SUCH_BUCKET, bucket, ex); } throw ex; } 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 14b27bfdc92..6598dac17db 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 @@ -21,7 +21,6 @@ import static org.apache.hadoop.ozone.audit.AuditLogger.PerformanceStringBuilder; import static org.apache.hadoop.ozone.s3.S3GatewayConfigKeys.OZONE_S3G_FSO_DIRECTORY_CREATION_ENABLED; import static org.apache.hadoop.ozone.s3.S3GatewayConfigKeys.OZONE_S3G_FSO_DIRECTORY_CREATION_ENABLED_DEFAULT; -import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.ENTITY_TOO_SMALL; import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.INVALID_ARGUMENT; import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.INVALID_REQUEST; import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.NO_SUCH_UPLOAD; @@ -179,36 +178,13 @@ public Response put( " considered as Unix Paths. Path has Violated FS Semantics " + "which caused put operation to fail."); throw os3Exception; - } else if (isAccessDenied(ex)) { - throw newError(S3ErrorTable.ACCESS_DENIED, keyPath, ex); - } else if (ex.getResult() == ResultCodes.QUOTA_EXCEEDED) { - throw newError(S3ErrorTable.QUOTA_EXCEEDED, keyPath, ex); - } else if (ex.getResult() == ResultCodes.BUCKET_NOT_FOUND) { - throw newError(S3ErrorTable.NO_SUCH_BUCKET, bucketName, ex); - } else if (ex.getResult() == ResultCodes.FILE_ALREADY_EXISTS) { - throw newError(S3ErrorTable.NO_OVERWRITE, keyPath, ex); - } else if (ex.getResult() == ResultCodes.KEY_ALREADY_EXISTS) { - throw newError(PRECOND_FAILED, keyPath, ex); - } else if (ex.getResult() == ResultCodes.ATOMIC_WRITE_CONFLICT) { - throw newError(S3ErrorTable.CONDITIONAL_REQUEST_CONFLICT, keyPath, ex); - } else if (ex.getResult() == ResultCodes.ETAG_MISMATCH) { - throw newError(PRECOND_FAILED, keyPath, ex); - } else if (ex.getResult() == ResultCodes.ETAG_NOT_AVAILABLE) { - throw newError(PRECOND_FAILED, keyPath, ex); - } else if (ex.getResult() == ResultCodes.INVALID_REQUEST) { - throw newError(S3ErrorTable.INVALID_REQUEST, keyPath); } else if (ex.getResult() == ResultCodes.KEY_NOT_FOUND && getHeaders().getHeaderString(S3Consts.IF_MATCH_HEADER) != null) { // If-Match failed because the key doesn't exist throw newError(PRECOND_FAILED, keyPath, ex); - } else if (ex.getResult() == ResultCodes.KEY_NOT_FOUND) { - throw newError(S3ErrorTable.NO_SUCH_KEY, keyPath); - } else if (ex.getResult() == ResultCodes.NOT_SUPPORTED_OPERATION) { - // e.g. if putObjectTagging operation is applied on FSO directory - throw newError(S3ErrorTable.NOT_IMPLEMENTED, keyPath); } - throw ex; + throw newError(bucketName, keyPath, ex); } } @@ -388,15 +364,7 @@ public Response get( try { return handler.handleGetRequest(context, keyPath); } catch (OMException ex) { - if (ex.getResult() == ResultCodes.KEY_NOT_FOUND) { - throw newError(S3ErrorTable.NO_SUCH_KEY, keyPath, ex); - } else if (isAccessDenied(ex)) { - throw newError(S3ErrorTable.ACCESS_DENIED, keyPath, ex); - } else if (ex.getResult() == ResultCodes.BUCKET_NOT_FOUND) { - throw newError(S3ErrorTable.NO_SUCH_BUCKET, bucketName, ex); - } else { - throw ex; - } + throw newError(bucketName, keyPath, ex); } } @@ -573,7 +541,7 @@ public Response head( OzoneKey key; try { if (S3Owner.hasBucketOwnershipVerificationConditions(getHeaders())) { - OzoneBucket bucket = getBucket(bucketName); + OzoneBucket bucket = getVolume().getBucket(bucketName); S3Owner.verifyBucketOwnerCondition(getHeaders(), bucketName, bucket.getOwner()); } key = getClientProtocol().headS3Object(bucketName, keyPath); @@ -593,12 +561,8 @@ public Response head( if (ex.getResult() == ResultCodes.KEY_NOT_FOUND) { // Just return 404 with no content return Response.status(Status.NOT_FOUND).build(); - } else if (isAccessDenied(ex)) { - throw newError(S3ErrorTable.ACCESS_DENIED, keyPath, ex); - } else if (ex.getResult() == ResultCodes.BUCKET_NOT_FOUND) { - throw newError(S3ErrorTable.NO_SUCH_BUCKET, bucketName, ex); } else { - throw ex; + throw newError(bucketName, keyPath, ex); } } catch (Exception ex) { auditReadFailure(s3GAction, ex); @@ -647,7 +611,6 @@ Example of such app is Trino (through Hive connector). * for more details. */ @DELETE - @SuppressWarnings("emptyblock") public Response delete( @PathParam(BUCKET) String bucketName, @PathParam(PATH) String keyPath @@ -656,9 +619,7 @@ public Response delete( try { return handler.handleDeleteRequest(context, keyPath); } catch (OMException ex) { - if (ex.getResult() == ResultCodes.BUCKET_NOT_FOUND) { - throw newError(S3ErrorTable.NO_SUCH_BUCKET, bucketName, ex); - } else if (ex.getResult() == ResultCodes.KEY_NOT_FOUND) { + if (ex.getResult() == ResultCodes.KEY_NOT_FOUND) { //NOT_FOUND is not a problem, AWS doesn't throw exception for missing // keys. Just return 204 return Response.status(Status.NO_CONTENT).build(); @@ -668,13 +629,8 @@ public Response delete( // NOT_FOUND is not a problem, AWS doesn't throw exception for missing // keys. Just return 204 return Response.status(Status.NO_CONTENT).build(); - } else if (isAccessDenied(ex)) { - throw newError(S3ErrorTable.ACCESS_DENIED, keyPath, ex); - } else if (ex.getResult() == ResultCodes.NOT_SUPPORTED_OPERATION) { - // When deleteObjectTagging operation is applied on FSO directory - throw S3ErrorTable.newError(S3ErrorTable.NOT_IMPLEMENTED, keyPath); } else { - throw ex; + throw newError(bucketName, keyPath, ex); } } } @@ -715,7 +671,7 @@ public Response initializeMultipartUpload( S3GAction s3GAction = S3GAction.INIT_MULTIPART_UPLOAD; try { - OzoneBucket ozoneBucket = getBucket(bucket); + OzoneBucket ozoneBucket = getVolume().getBucket(bucket); S3Owner.verifyBucketOwnerCondition(getHeaders(), bucket, ozoneBucket.getOwner()); Map customMetadata = @@ -742,10 +698,7 @@ public Response initializeMultipartUpload( } catch (OMException ex) { auditWriteFailure(s3GAction, ex); getMetrics().updateInitMultipartUploadFailureStats(startNanos); - if (isAccessDenied(ex)) { - throw newError(S3ErrorTable.ACCESS_DENIED, key, ex); - } - throw ex; + throw newError(bucket, key, ex); } catch (Exception ex) { auditWriteFailure(s3GAction, ex); getMetrics().updateInitMultipartUploadFailureStats(startNanos); @@ -800,14 +753,8 @@ public Response completeMultipartUpload( } catch (OMException ex) { auditWriteFailure(s3GAction, ex); getMetrics().updateCompleteMultipartUploadFailureStats(startNanos); - if (ex.getResult() == ResultCodes.INVALID_PART) { - throw newError(S3ErrorTable.INVALID_PART, key, ex); - } else if (ex.getResult() == ResultCodes.INVALID_PART_ORDER) { - throw newError(S3ErrorTable.INVALID_PART_ORDER, key, ex); - } else if (ex.getResult() == ResultCodes.NO_SUCH_MULTIPART_UPLOAD_ERROR) { + if (ex.getResult() == ResultCodes.NO_SUCH_MULTIPART_UPLOAD_ERROR) { throw newError(NO_SUCH_UPLOAD, uploadID, ex); - } else if (ex.getResult() == ResultCodes.ENTITY_TOO_SMALL) { - throw newError(ENTITY_TOO_SMALL, key, ex); } else if (ex.getResult() == ResultCodes.INVALID_REQUEST) { OS3Exception os3Exception = newError(INVALID_REQUEST, key, ex); os3Exception.setErrorMessage("An error occurred (InvalidRequest) " + @@ -822,12 +769,11 @@ public Response completeMultipartUpload( "considered as Unix Paths. A directory already exists with a " + "given KeyName caused failure for MPU"); throw os3Exception; - } else if (ex.getResult() == ResultCodes.BUCKET_NOT_FOUND) { - throw newError(S3ErrorTable.NO_SUCH_BUCKET, bucket, ex); } - throw ex; + throw newError(bucket, key, ex); } catch (Exception ex) { auditWriteFailure(s3GAction, ex); + getMetrics().updateCompleteMultipartUploadFailureStats(startNanos); throw ex; } } @@ -988,15 +934,13 @@ private Response createMultipartKey(OzoneVolume volume, OzoneBucket ozoneBucket, } if (ex.getResult() == ResultCodes.NO_SUCH_MULTIPART_UPLOAD_ERROR) { throw newError(NO_SUCH_UPLOAD, uploadID, ex); - } else if (isAccessDenied(ex)) { - throw newError(S3ErrorTable.ACCESS_DENIED, bucketName + "/" + key, ex); } else if (ex.getResult() == ResultCodes.INVALID_PART) { OS3Exception os3Exception = newError( S3ErrorTable.INVALID_ARGUMENT, String.valueOf(partNumber), ex); os3Exception.setErrorMessage(ex.getMessage()); throw os3Exception; } - throw ex; + throw newError(bucketName, key, ex); } finally { // Reset the thread-local message digest instance in case of exception // and MessageDigest#digest is never called @@ -1143,11 +1087,8 @@ private CopyObjectResponse copyObject(OzoneVolume volume, throw newError(S3ErrorTable.NO_SUCH_KEY, sourceKey, ex); } else if (ex.getResult() == ResultCodes.BUCKET_NOT_FOUND) { throw newError(S3ErrorTable.NO_SUCH_BUCKET, sourceBucket, ex); - } else if (isAccessDenied(ex)) { - throw newError(S3ErrorTable.ACCESS_DENIED, - destBucket + "/" + destkey, ex); } - throw ex; + throw newError(destBucket + "/" + destkey, ex); } finally { // Reset the thread-local message digest instance in case of exception // and MessageDigest#digest is never called diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java index 7d385749556..721cb57e8fc 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java @@ -238,6 +238,11 @@ public static S3ErrorTable translateResultCode(OMException ex) { } } + public static OS3Exception newError(String bucket, String resource, OMException e) { + S3ErrorTable err = translateResultCode(e); + return newError(err, NO_SUCH_BUCKET == err ? bucket : resource, e); + } + public static OS3Exception newError(String resource, OMException e) { return newError(translateResultCode(e), resource, e); } From bf49cf2cdb92fbedd986d01f83d9f4f37929df61 Mon Sep 17 00:00:00 2001 From: "Doroszlai, Attila" Date: Mon, 20 Apr 2026 10:08:43 +0200 Subject: [PATCH 4/9] HDDS-15000. Improve S3 audit log stack traces --- .../ozone/s3/exception/OS3Exception.java | 6 ++- .../ozone/s3/exception/S3ErrorTable.java | 43 +++++++++---------- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/OS3Exception.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/OS3Exception.java index 2551fbe0ca1..2b6affd3c03 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/OS3Exception.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/OS3Exception.java @@ -66,12 +66,16 @@ public OS3Exception() { //Added for JaxB. } - OS3Exception(S3ErrorTable error, Exception cause) { + OS3Exception(S3ErrorTable error, Exception cause, String resource) { super(error.getErrorMessage(), cause); code = error.getCode(); errorMessage = error.getErrorMessage(); httpCode = error.getHttpCode(); + this.resource = resource; + + // logging in S3ErrorTable to respect any existing log level setting + S3ErrorTable.log(this); } public String getCode() { diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java index 721cb57e8fc..2884fe0a885 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java @@ -27,6 +27,7 @@ import static org.apache.hadoop.ozone.OzoneConsts.S3_REQUEST_HEADER_METADATA_SIZE_LIMIT_KB; import static org.apache.hadoop.ozone.s3.util.S3Consts.RANGE_NOT_SATISFIABLE; +import jakarta.annotation.Nullable; import org.apache.hadoop.ozone.om.exceptions.OMException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -238,35 +239,33 @@ public static S3ErrorTable translateResultCode(OMException ex) { } } - public static OS3Exception newError(String bucket, String resource, OMException e) { - S3ErrorTable err = translateResultCode(e); - return newError(err, NO_SUCH_BUCKET == err ? bucket : resource, e); + /** Same as {@link #newError(String, OMException)}, but uses {@code bucket} as the {@code resource} + * in case of {@link #NO_SUCH_BUCKET} error. */ + public static OS3Exception newError(String bucket, String resource, OMException cause) { + S3ErrorTable errorCode = translateResultCode(cause); + return new OS3Exception(errorCode, cause, NO_SUCH_BUCKET == errorCode ? bucket : resource); } - public static OS3Exception newError(String resource, OMException e) { - return newError(translateResultCode(e), resource, e); + /** Creates new {@link OS3Exception} for {@link OMException} and {@code resource}. */ + public static OS3Exception newError(@Nullable String resource, OMException cause) { + return new OS3Exception(translateResultCode(cause), cause, resource); } - public static OS3Exception newError(S3ErrorTable e, String resource) { - return newError(e, resource, null); + /** Creates new {@link OS3Exception} for {@link S3ErrorTable} and {@code resource}. */ + public static OS3Exception newError(S3ErrorTable errorCode, @Nullable String resource) { + return new OS3Exception(errorCode, null, resource); } - /** - * Create a new {@link OS3Exception} for the given error. - * @param e Error Template - * @param resource Resource associated with this exception - * @param ex the original exception, may be null - * @return creates a new instance of error based on the template - */ - public static OS3Exception newError(S3ErrorTable e, String resource, - Exception ex) { - OS3Exception err = new OS3Exception(e, ex); - err.setResource(resource); - if (e.getHttpCode() == HTTP_INTERNAL_ERROR) { - LOG.error("Internal Error: {}", err.toXml(), ex); + /** Creates new {@link OS3Exception} for {@link S3ErrorTable}, {@code resource} and {@code cause}. */ + public static OS3Exception newError(S3ErrorTable errorCode, @Nullable String resource, Exception cause) { + return new OS3Exception(errorCode, cause, resource); + } + + static void log(OS3Exception err) { + if (err.getHttpCode() == HTTP_INTERNAL_ERROR) { + LOG.error("Internal Error: {}", err.toXml(), err.getCause()); } else if (LOG.isDebugEnabled()) { - LOG.debug(err.toXml(), ex); + LOG.debug(err.toXml(), err.getCause()); } - return err; } } From 8e066b206f6959329cc7a2a52bfa610e0f947499 Mon Sep 17 00:00:00 2001 From: "Doroszlai, Attila" Date: Mon, 20 Apr 2026 10:20:29 +0200 Subject: [PATCH 5/9] pass cause to newError --- .../apache/hadoop/ozone/s3/endpoint/ObjectTaggingHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectTaggingHandler.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectTaggingHandler.java index 2a04ddb4fd1..3c5e756ed5b 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectTaggingHandler.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectTaggingHandler.java @@ -51,7 +51,7 @@ Response handlePutRequest(ObjectRequestContext context, String keyName, InputStr tagging = UNMARSHALLER.get().readFrom(body); tagging.validate(); } catch (Exception ex) { - OS3Exception exception = S3ErrorTable.newError(S3ErrorTable.MALFORMED_XML, keyName); + OS3Exception exception = S3ErrorTable.newError(S3ErrorTable.MALFORMED_XML, keyName, ex); exception.setErrorMessage(exception.getErrorMessage() + ". " + ex.getMessage()); throw exception; } From 964468e5a5bc732272a6c2cf8d410a6ea0986b9d Mon Sep 17 00:00:00 2001 From: "Doroszlai, Attila" Date: Mon, 20 Apr 2026 10:21:29 +0200 Subject: [PATCH 6/9] setErrorMessage without dummy exception --- .../ozone/s3/endpoint/ListMultipartUploadsHandler.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ListMultipartUploadsHandler.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ListMultipartUploadsHandler.java index 310074fd2ea..7f8efb3678d 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ListMultipartUploadsHandler.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ListMultipartUploadsHandler.java @@ -50,8 +50,9 @@ Response handleGetRequest(S3RequestContext context, String bucketName) final String uploadIdMarker = queryParams().get(QueryParams.UPLOAD_ID_MARKER); if (maxUploads < 1) { - throw newError(S3ErrorTable.INVALID_ARGUMENT, "max-uploads", - new Exception("max-uploads must be positive")); + OS3Exception e = newError(S3ErrorTable.INVALID_ARGUMENT, "max-uploads"); + e.setErrorMessage("max-uploads must be positive"); + throw e; } long startNanos = context.getStartNanos(); From 79af068df19059a4cf72c466c613f638b399f32d Mon Sep 17 00:00:00 2001 From: "Doroszlai, Attila" Date: Mon, 20 Apr 2026 10:37:35 +0200 Subject: [PATCH 7/9] remove newError null arguments --- .../hadoop/ozone/s3/AuthorizationFilter.java | 7 +++---- ...CompleteMultipartUploadRequestUnmarshaller.java | 4 ++-- .../ozone/s3/endpoint/MessageUnmarshaller.java | 2 +- .../hadoop/ozone/s3/exception/S3ErrorTable.java | 10 ++++++++++ .../ozone/s3/signature/StringToSignProducer.java | 14 +++++++------- 5 files changed, 23 insertions(+), 14 deletions(-) diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/AuthorizationFilter.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/AuthorizationFilter.java index c38992ce50d..ae4ce9bada8 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/AuthorizationFilter.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/AuthorizationFilter.java @@ -71,14 +71,14 @@ public void filter(ContainerRequestContext context) throws } else { LOG.debug("Unsupported AWS signature version: {}", signatureInfo.getVersion()); - throw S3ErrorTable.newError(S3_AUTHINFO_CREATION_ERROR, String.valueOf(signatureInfo.getVersion()), null); + throw S3ErrorTable.newError(S3_AUTHINFO_CREATION_ERROR, String.valueOf(signatureInfo.getVersion())); } String awsAccessId = signatureInfo.getAwsAccessId(); // ONLY validate aws access id when needed. if (awsAccessId == null || awsAccessId.equals("")) { LOG.debug("Malformed s3 header. awsAccessID: {}", awsAccessId); - throw S3ErrorTable.newError(ACCESS_DENIED, null, null); + throw S3ErrorTable.newError(ACCESS_DENIED); } } catch (OS3Exception ex) { LOG.debug("Error during Client Creation: ", ex); @@ -87,8 +87,7 @@ public void filter(ContainerRequestContext context) throws // For any other critical errors during object creation throw Internal // error. LOG.debug("Error during Client Creation: ", e); - throw wrapOS3Exception( - S3ErrorTable.newError(INTERNAL_ERROR, null, e)); + throw wrapOS3Exception(S3ErrorTable.newError(INTERNAL_ERROR, e)); } } diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/CompleteMultipartUploadRequestUnmarshaller.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/CompleteMultipartUploadRequestUnmarshaller.java index 763e861a7ef..afee2747677 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/CompleteMultipartUploadRequestUnmarshaller.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/CompleteMultipartUploadRequestUnmarshaller.java @@ -51,12 +51,12 @@ public CompleteMultipartUploadRequest readFrom( InputStream inputStream) throws WebApplicationException { try { if (inputStream.available() == 0) { - throw wrapOS3Exception(newError(INVALID_REQUEST, null, null) + throw wrapOS3Exception(newError(INVALID_REQUEST) .withMessage("You must specify at least one part")); } return super.readFrom(aClass, type, annotations, mediaType, multivaluedMap, inputStream); } catch (IOException e) { - throw wrapOS3Exception(newError(INVALID_REQUEST, null, e) + throw wrapOS3Exception(newError(INVALID_REQUEST, e) .withMessage(e.getMessage())); } } diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/MessageUnmarshaller.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/MessageUnmarshaller.java index a9366975075..90c1835b8d6 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/MessageUnmarshaller.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/MessageUnmarshaller.java @@ -82,7 +82,7 @@ public T readFrom( filter.parse(new InputSource(inputStream)); return cls.cast(unmarshallerHandler.getResult()); } catch (Exception e) { - throw wrapOS3Exception(newError(INVALID_REQUEST, null, e).withMessage(e.getMessage())); + throw wrapOS3Exception(newError(INVALID_REQUEST, e).withMessage(e.getMessage())); } } diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java index 2884fe0a885..de69bd16ebf 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java @@ -251,11 +251,21 @@ public static OS3Exception newError(@Nullable String resource, OMException cause return new OS3Exception(translateResultCode(cause), cause, resource); } + /** Creates new {@link OS3Exception} for {@link S3ErrorTable}. */ + public static OS3Exception newError(S3ErrorTable errorCode) { + return new OS3Exception(errorCode, null, null); + } + /** Creates new {@link OS3Exception} for {@link S3ErrorTable} and {@code resource}. */ public static OS3Exception newError(S3ErrorTable errorCode, @Nullable String resource) { return new OS3Exception(errorCode, null, resource); } + /** Creates new {@link OS3Exception} for {@link S3ErrorTable}, {@code resource} and {@code cause}. */ + public static OS3Exception newError(S3ErrorTable errorCode, Exception cause) { + return new OS3Exception(errorCode, cause, null); + } + /** Creates new {@link OS3Exception} for {@link S3ErrorTable}, {@code resource} and {@code cause}. */ public static OS3Exception newError(S3ErrorTable errorCode, @Nullable String resource, Exception cause) { return new OS3Exception(errorCode, cause, resource); diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/StringToSignProducer.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/StringToSignProducer.java index a154aa781fe..f308363133d 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/StringToSignProducer.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/StringToSignProducer.java @@ -115,7 +115,7 @@ public static String createSignatureBase( strToSign.append(signatureInfo.getAlgorithm()).append(NEWLINE); if (signatureInfo.getDateTime() == null) { LOG.error("DateTime Header not found."); - throw newError(S3_AUTHINFO_CREATION_ERROR, null, null); + throw newError(S3_AUTHINFO_CREATION_ERROR); } strToSign.append(signatureInfo.getDateTime()).append(NEWLINE) .append(credentialScope).append(NEWLINE); @@ -188,13 +188,13 @@ public static String buildCanonicalRequest( validateSignedHeader(schema, header, headerValue); } catch (DateTimeParseException ex) { LOG.error("DateTime format invalid.", ex); - throw newError(S3_AUTHINFO_CREATION_ERROR, null, null); + throw newError(S3_AUTHINFO_CREATION_ERROR); } } else { LOG.error("Header " + header + " not present in " + "request but requested to be signed."); - throw newError(S3_AUTHINFO_CREATION_ERROR, null, null); + throw newError(S3_AUTHINFO_CREATION_ERROR); } } @@ -226,7 +226,7 @@ private static String getPayloadHash(Map headers, boolean isUsin if (contentSignatureHeaderValue == null) { LOG.error("The request must include " + X_AMZ_CONTENT_SHA256 + " header for signed payload"); - throw newError(S3_AUTHINFO_CREATION_ERROR, null, null); + throw newError(S3_AUTHINFO_CREATION_ERROR); } // Simply return the header value of x-amz-content-sha256 as the payload hash // These are the possible cases: @@ -333,7 +333,7 @@ static void validateSignedHeader( LOG.error("AWS date not in valid range. Request timestamp:{} should " + "not be older than {} seconds.", headerValue, PRESIGN_URL_MAX_EXPIRATION_SECONDS); - throw newError(S3_AUTHINFO_CREATION_ERROR, null, null); + throw newError(S3_AUTHINFO_CREATION_ERROR); } break; case X_AMZ_CONTENT_SHA256: @@ -362,7 +362,7 @@ private static void validateCanonicalHeaders( ) throws OS3Exception { if (!canonicalHeaders.contains(HOST + ":")) { LOG.error("The SignedHeaders list must include HTTP Host header"); - throw newError(S3_AUTHINFO_CREATION_ERROR, null, null); + throw newError(S3_AUTHINFO_CREATION_ERROR); } for (String header : headers.keySet().stream() .filter(s -> s.startsWith("x-amz-")) @@ -376,7 +376,7 @@ private static void validateCanonicalHeaders( } LOG.error("The SignedHeaders list must include all " + "x-amz-* headers in the request"); - throw newError(S3_AUTHINFO_CREATION_ERROR, null, null); + throw newError(S3_AUTHINFO_CREATION_ERROR); } } } From 85b6ff7605cabbd31cb975ccb74646a307754a8d Mon Sep 17 00:00:00 2001 From: "Doroszlai, Attila" Date: Mon, 20 Apr 2026 13:03:46 +0200 Subject: [PATCH 8/9] move non-error NOT_FOUND case into handleDeleteRequest --- .../ozone/s3/endpoint/ObjectEndpoint.java | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) 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 6598dac17db..60a5f742141 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 @@ -619,19 +619,7 @@ public Response delete( try { return handler.handleDeleteRequest(context, keyPath); } catch (OMException ex) { - if (ex.getResult() == ResultCodes.KEY_NOT_FOUND) { - //NOT_FOUND is not a problem, AWS doesn't throw exception for missing - // keys. Just return 204 - return Response.status(Status.NO_CONTENT).build(); - } else if (ex.getResult() == ResultCodes.DIRECTORY_NOT_EMPTY) { - // With PREFIX metadata layout, a dir deletion without recursive flag - // to true will throw DIRECTORY_NOT_EMPTY error for a non-empty dir. - // NOT_FOUND is not a problem, AWS doesn't throw exception for missing - // keys. Just return 204 - return Response.status(Status.NO_CONTENT).build(); - } else { - throw newError(bucketName, keyPath, ex); - } + throw newError(bucketName, keyPath, ex); } } @@ -648,7 +636,20 @@ Response handleDeleteRequest(ObjectRequestContext context, String keyPath) getMetrics().updateDeleteKeySuccessStats(startNanos); return Response.status(Status.NO_CONTENT).build(); - + } catch (OMException ex) { + getMetrics().updateDeleteKeyFailureStats(startNanos); + if (ex.getResult() == ResultCodes.KEY_NOT_FOUND) { + //NOT_FOUND is not a problem, AWS doesn't throw exception for missing + // keys. Just return 204 + return Response.status(Status.NO_CONTENT).build(); + } else if (ex.getResult() == ResultCodes.DIRECTORY_NOT_EMPTY) { + // With PREFIX metadata layout, a dir deletion without recursive flag + // to true will throw DIRECTORY_NOT_EMPTY error for a non-empty dir. + // NOT_FOUND is not a problem, AWS doesn't throw exception for missing + // keys. Just return 204 + return Response.status(Status.NO_CONTENT).build(); + } + throw newError(context.getBucketName(), keyPath, ex); } catch (Exception ex) { getMetrics().updateDeleteKeyFailureStats(startNanos); throw ex; From 9d639754df182815f87ff361e2d6137f16a3c3b7 Mon Sep 17 00:00:00 2001 From: "Doroszlai, Attila" Date: Tue, 21 Apr 2026 07:21:24 +0200 Subject: [PATCH 9/9] fix javadoc typos --- .../org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java index de69bd16ebf..022dc08949e 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java @@ -195,7 +195,7 @@ public int getHttpCode() { return httpCode; } - /** Converts result code of @{code OMException} to {@code S3ErrorTable}, which + /** Converts result code of {@code OMException} to {@code S3ErrorTable}, which * should be thrown via {@link #newError(S3ErrorTable, String, Exception)}. */ public static S3ErrorTable translateResultCode(OMException ex) { switch (ex.getResult()) { @@ -261,7 +261,7 @@ public static OS3Exception newError(S3ErrorTable errorCode, @Nullable String res return new OS3Exception(errorCode, null, resource); } - /** Creates new {@link OS3Exception} for {@link S3ErrorTable}, {@code resource} and {@code cause}. */ + /** Creates new {@link OS3Exception} for {@link S3ErrorTable} and {@code cause}. */ public static OS3Exception newError(S3ErrorTable errorCode, Exception cause) { return new OS3Exception(errorCode, cause, null); }