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 4a832912fea HDDS-16591. Reject not implemented S3 subresource APIs
(#11313)
4a832912fea is described below
commit 4a832912feae49664e05479d0ea9ba2c182e905e
Author: fmorg-git <[email protected]>
AuthorDate: Wed Sep 30 23:55:58 2026 -0700
HDDS-16591. Reject not implemented S3 subresource APIs (#11313)
---
.../ozone/s3/awssdk/v1/AbstractS3SDKV1Tests.java | 48 +++++++--
.../ozone/s3/awssdk/v2/AbstractS3SDKV2Tests.java | 86 ++++++++++++++--
.../org/apache/hadoop/ozone/audit/S3GAction.java | 6 +-
.../hadoop/ozone/s3/endpoint/BucketAclHandler.java | 5 +
.../hadoop/ozone/s3/endpoint/BucketEndpoint.java | 6 ++
.../s3/endpoint/BucketGetLocationHandler.java | 5 +
.../ozone/s3/endpoint/BucketOperationHandler.java | 21 +++-
.../BucketOperationNotImplementedHandler.java | 64 ++++++++++++
.../hadoop/ozone/s3/endpoint/EndpointBase.java | 25 +++++
.../s3/endpoint/ListMultipartUploadsHandler.java | 5 +
.../hadoop/ozone/s3/endpoint/ObjectAclHandler.java | 15 ++-
.../ozone/s3/endpoint/ObjectAttributesHandler.java | 5 +
.../hadoop/ozone/s3/endpoint/ObjectEndpoint.java | 1 +
.../ozone/s3/endpoint/ObjectGetTorrentHandler.java | 5 +
.../ozone/s3/endpoint/ObjectOperationHandler.java | 17 +++-
.../ObjectOperationNotImplementedHandler.java | 65 ++++++++++++
.../ozone/s3/endpoint/ObjectTaggingHandler.java | 12 ++-
.../org/apache/hadoop/ozone/s3/util/S3Consts.java | 35 +++++++
.../hadoop/ozone/s3/util/S3GActionIamMapper.java | 2 +
.../s3/endpoint/TestBucketNotImplemented.java | 102 +++++++++++++++++++
.../ozone/s3/endpoint/TestObjectMultiDelete.java | 24 +++++
.../s3/endpoint/TestObjectNotImplemented.java | 113 +++++++++++++++++++++
.../ozone/s3/endpoint/TestObjectTaggingDelete.java | 12 +++
.../ozone/s3/endpoint/TestObjectTaggingPut.java | 8 ++
.../ozone/s3/util/TestS3GActionIamMapper.java | 2 +
25 files changed, 661 insertions(+), 28 deletions(-)
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 07dae405a12..246eae25011 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
@@ -54,8 +54,10 @@
import com.amazonaws.services.s3.model.CreateBucketRequest;
import com.amazonaws.services.s3.model.DeleteBucketTaggingConfigurationRequest;
import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest;
+import com.amazonaws.services.s3.model.GetBucketAccelerateConfigurationRequest;
import com.amazonaws.services.s3.model.GetBucketLifecycleConfigurationRequest;
import com.amazonaws.services.s3.model.GetBucketTaggingConfigurationRequest;
+import com.amazonaws.services.s3.model.GetObjectAclRequest;
import com.amazonaws.services.s3.model.GetObjectRequest;
import com.amazonaws.services.s3.model.GetObjectTaggingRequest;
import com.amazonaws.services.s3.model.GetObjectTaggingResult;
@@ -162,6 +164,7 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestMethodOrder;
+import org.junit.jupiter.api.function.Executable;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
@@ -1394,11 +1397,7 @@ public void testPutObjectACL() throws Exception {
SetObjectAclRequest setObjectAclRequest = new
SetObjectAclRequest(bucketName, keyName, aclList);
- AmazonServiceException ase = assertThrows(AmazonServiceException.class,
- () -> s3Client.setObjectAcl(setObjectAclRequest));
- assertEquals("NotImplemented", ase.getErrorCode());
- assertEquals(501, ase.getStatusCode());
- assertEquals(ErrorType.Service, ase.getErrorType());
+ assertNotImplemented(() -> s3Client.setObjectAcl(setObjectAclRequest));
// Ensure that the object content remains unchanged
ObjectMetadata updatedObjectMetadata =
s3Client.getObjectMetadata(bucketName, keyName);
@@ -1416,6 +1415,27 @@ public void testPutObjectACL() throws Exception {
}
}
+ @Test
+ public void testGetObjectAclNotImplemented() throws Exception {
+ final String bucketName = getBucketName();
+ final String keyName = getKeyName();
+ final String content = "bar";
+ s3Client.createBucket(bucketName);
+ s3Client.putObject(
+ bucketName, keyName, new
ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)), new
ObjectMetadata());
+
+ assertNotImplemented(() -> s3Client.getObjectAcl(new
GetObjectAclRequest(bucketName, keyName)));
+ }
+
+ @Test
+ public void testGetBucketAccelerateConfigurationNotImplemented() {
+ final String bucketName = getBucketName();
+ s3Client.createBucket(bucketName);
+
+ assertNotImplemented(
+ () -> s3Client.getBucketAccelerateConfiguration(new
GetBucketAccelerateConfigurationRequest(bucketName)));
+ }
+
@Test
public void testGetObject() throws Exception {
final String bucketName = getBucketName();
@@ -2561,19 +2581,20 @@ public void testPresignedUrlGet() throws IOException {
}
}
- @Test
- public void testPresignedUrlGetObjectTorrentNotImplemented() throws
Exception {
+ @ParameterizedTest
+ @ValueSource(strings = {"annotation", "torrent"})
+ public void testPresignedUrlGetObjectSubresourceNotImplemented(String
subresource) throws Exception {
final String keyName = getKeyName();
InputStream is = new
ByteArrayInputStream(CONTENT.getBytes(StandardCharsets.UTF_8));
s3Client.putObject(BUCKET_NAME, keyName, is, new ObjectMetadata());
- // AmazonS3 (SDK v1) has no getObjectTorrent API, so exercise the same
HTTP behavior
- // via a presigned URL with the torrent query parameter, as with other
request shapes
+ // AmazonS3 (SDK v1) has no getObjectTorrent or listObjectAnnotations
API, so exercise the same HTTP behavior
+ // via a presigned URL with the subresource query parameter, as with
other request shapes
// the typed v1 API doesn't expose.
GeneratePresignedUrlRequest generatePresignedUrlRequest =
new GeneratePresignedUrlRequest(BUCKET_NAME,
keyName).withMethod(HttpMethod.GET).withExpiration(expiration);
- generatePresignedUrlRequest.addRequestParameter("torrent", "");
+ generatePresignedUrlRequest.addRequestParameter(subresource, "");
URL presignedUrl =
s3Client.generatePresignedUrl(generatePresignedUrlRequest);
HttpURLConnection connection = null;
@@ -2977,6 +2998,13 @@ private String getKeyName(String ignored) {
return uniqueObjectName();
}
+ private static void assertNotImplemented(Executable request) {
+ final AmazonServiceException ase =
assertThrows(AmazonServiceException.class, request);
+ assertEquals(S3ErrorTable.NOT_IMPLEMENTED.getCode(), ase.getErrorCode());
+ assertEquals(501, ase.getStatusCode());
+ assertEquals(ErrorType.Service, ase.getErrorType());
+ }
+
private String multipartUpload(String bucketName, String key, File file,
long partSize, String contentType,
Map<String, String> userMetadata, List<Tag>
tags) throws Exception {
String uploadId = initiateMultipartUpload(bucketName, key, contentType,
userMetadata, tags);
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 28c4efad510..5a1577c7890 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
@@ -168,6 +168,7 @@
import software.amazon.awssdk.services.s3.model.NoSuchKeyException;
import software.amazon.awssdk.services.s3.model.ObjectAttributes;
import software.amazon.awssdk.services.s3.model.ObjectIdentifier;
+import software.amazon.awssdk.services.s3.model.ObjectLockLegalHoldStatus;
import software.amazon.awssdk.services.s3.model.PutBucketAclRequest;
import software.amazon.awssdk.services.s3.model.PutBucketTaggingRequest;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
@@ -677,18 +678,84 @@ public void testGetObjectTorrentNotImplemented() {
s3Client.createBucket(b -> b.bucket(bucketName));
s3Client.putObject(b -> b.bucket(bucketName).key(keyName),
RequestBody.fromString(content));
- S3Exception exception = assertThrows(S3Exception.class,
- () -> s3Client.getObjectTorrent(b ->
b.bucket(bucketName).key(keyName)));
-
- assertEquals(501, exception.statusCode());
- assertEquals(S3ErrorTable.NOT_IMPLEMENTED.getCode(),
exception.awsErrorDetails().errorCode());
- assertEquals(S3ErrorTable.NOT_IMPLEMENTED.getErrorMessage(),
exception.awsErrorDetails().errorMessage());
+ assertNotImplemented(() -> s3Client.getObjectTorrent(b ->
b.bucket(bucketName).key(keyName)));
// object must be untouched
HeadObjectResponse headObjectResponse = s3Client.headObject(b ->
b.bucket(bucketName).key(keyName));
assertEquals(content.length(), headObjectResponse.contentLength());
}
+ @Test
+ public void testGetObjectAclNotImplemented() {
+ final String bucketName = getBucketName();
+ final String keyName = getKeyName();
+ s3Client.createBucket(b -> b.bucket(bucketName));
+ s3Client.putObject(b -> b.bucket(bucketName).key(keyName),
RequestBody.fromString("bar"));
+
+ assertNotImplemented(() -> s3Client.getObjectAcl(b ->
b.bucket(bucketName).key(keyName)));
+ }
+
+ @Test
+ public void testGetBucketAccelerateConfigurationNotImplemented() {
+ final String bucketName = getBucketName();
+ s3Client.createBucket(b -> b.bucket(bucketName));
+
+ assertNotImplemented(() -> s3Client.getBucketAccelerateConfiguration(b ->
b.bucket(bucketName)));
+ }
+
+ @Test
+ public void testListObjectAnnotationsNotImplemented() {
+ final String bucketName = getBucketName();
+ final String keyName = getKeyName();
+ s3Client.createBucket(b -> b.bucket(bucketName));
+ s3Client.putObject(b -> b.bucket(bucketName).key(keyName),
RequestBody.fromString("bar"));
+
+ assertNotImplemented(() -> s3Client.listObjectAnnotations(b ->
b.bucket(bucketName).key(keyName)));
+ }
+
+ @Test
+ public void testDeleteBucketMetadataTableConfigurationNotImplemented() {
+ final String bucketName = getBucketName();
+ s3Client.createBucket(b -> b.bucket(bucketName));
+
+ assertNotImplemented(() ->
s3Client.deleteBucketMetadataTableConfiguration(b -> b.bucket(bucketName)));
+
+ // the empty bucket must not be deleted
+ assertDoesNotThrow(() -> s3Client.headBucket(b -> b.bucket(bucketName)));
+ }
+
+ @Test
+ public void testDeleteObjectsWithVersionIdNotImplemented() {
+ final String bucketName = getBucketName();
+ final String keyName = getKeyName();
+ s3Client.createBucket(b -> b.bucket(bucketName));
+ s3Client.putObject(b -> b.bucket(bucketName).key(keyName),
RequestBody.fromString("bar"));
+
+ assertNotImplemented(
+ () -> s3Client.deleteObjects(
+ b -> b.bucket(bucketName).delete(d ->
d.objects(ObjectIdentifier.builder().key(keyName)
+ .versionId("nonexistent").build()))));
+
+ // the current object must not be deleted
+ assertDoesNotThrow(() -> s3Client.headObject(b ->
b.bucket(bucketName).key(keyName)));
+ }
+
+ @Test
+ public void testPutObjectLegalHoldNotImplemented() {
+ final String bucketName = getBucketName();
+ final String keyName = getKeyName();
+ final String content = "bar";
+ s3Client.createBucket(b -> b.bucket(bucketName));
+ s3Client.putObject(b -> b.bucket(bucketName).key(keyName),
RequestBody.fromString(content));
+
+ assertNotImplemented(
+ () -> s3Client.putObjectLegalHold(
+ b -> b.bucket(bucketName).key(keyName).legalHold(h ->
h.status(ObjectLockLegalHoldStatus.ON))));
+
+ // the object must not be overwritten with the request body
+ assertEquals(content, s3Client.getObjectAsBytes(b ->
b.bucket(bucketName).key(keyName)).asUtf8String());
+ }
+
@Test
public void testHeadObjectIfMatch() {
final String bucketName = getBucketName();
@@ -3100,6 +3167,13 @@ private String getKeyName(String ignored) {
return uniqueObjectName();
}
+ private static void assertNotImplemented(Executable request) {
+ final S3Exception exception = assertThrows(S3Exception.class, request);
+ assertEquals(501, exception.statusCode());
+ assertEquals(S3ErrorTable.NOT_IMPLEMENTED.getCode(),
exception.awsErrorDetails().errorCode());
+ assertEquals(S3ErrorTable.NOT_IMPLEMENTED.getErrorMessage(),
exception.awsErrorDetails().errorMessage());
+ }
+
private String multipartUpload(String bucketName, String key, File file, int
partSize,
Map<String, String> userMetadata, List<Tag>
tags) throws Exception {
String uploadId = initiateMultipartUpload(bucketName, key, userMetadata,
tags);
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/audit/S3GAction.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/audit/S3GAction.java
index f215cd6cb0f..0171d19f9a6 100644
---
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/audit/S3GAction.java
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/audit/S3GAction.java
@@ -62,13 +62,17 @@ public enum S3GAction implements AuditAction {
GET_BUCKET_TAGGING,
PUT_BUCKET_TAGGING,
DELETE_BUCKET_TAGGING,
+ GET_OBJECT_ACL,
PUT_OBJECT_ACL,
// STS endpoint
ASSUME_ROLE,
GET_CALLER_IDENTITY,
- GET_OBJECT_ATTRIBUTES;
+ GET_OBJECT_ATTRIBUTES,
+
+ // bucket and object subresource operations that are not implemented
+ NOT_IMPLEMENTED;
@Override
public String getAction() {
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketAclHandler.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketAclHandler.java
index bc9e9dca281..c68bb5915d6 100644
---
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketAclHandler.java
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketAclHandler.java
@@ -70,6 +70,11 @@ private boolean shouldHandle() {
return queryParams().get(QueryParams.ACL) != null;
}
+ @Override
+ S3GAction getAction() {
+ return shouldHandle() ? S3GAction.NOT_IMPLEMENTED : null;
+ }
+
/**
* Implement acl get.
* <p>
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 497f4efc76b..298ee53e8a7 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
@@ -352,6 +352,11 @@ public MultiDeleteResponse multiDelete(
throw newError(S3ErrorTable.MALFORMED_XML, bucketName);
}
+ // Deleting a specific version is not implemented; ignoring VersionId
would delete the current object instead.
+ if (request.getObjects() != null &&
request.getObjects().stream().anyMatch(o -> o.getVersionId() != null)) {
+ throw newError(S3ErrorTable.NOT_IMPLEMENTED, bucketName);
+ }
+
final OzoneBucket bucket;
try {
bucket = getVolume().getBucket(bucketName);
@@ -456,6 +461,7 @@ protected void init() {
.add(new ListMultipartUploadsHandler())
.add(new BucketTaggingHandler())
.add(new BucketLifecycleHandler())
+ .add(new BucketOperationNotImplementedHandler())
.add(new BucketCrudHandler())
.add(this)
.build();
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketGetLocationHandler.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketGetLocationHandler.java
index b01daf14e85..256a150fdbf 100644
---
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketGetLocationHandler.java
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketGetLocationHandler.java
@@ -44,4 +44,9 @@ Response handleGetRequest(S3RequestContext context, String
bucketName)
context.setAction(S3GAction.GET_BUCKET_LOCATION);
throw newError(NOT_IMPLEMENTED, "GetBucketLocation");
}
+
+ @Override
+ S3GAction getAction() {
+ return queryParams().get(QueryParams.LOCATION) == null ? null :
S3GAction.NOT_IMPLEMENTED;
+ }
}
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketOperationHandler.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketOperationHandler.java
index e557d28f16d..7b74583ae6b 100644
---
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketOperationHandler.java
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketOperationHandler.java
@@ -20,6 +20,7 @@
import java.io.IOException;
import java.io.InputStream;
import javax.ws.rs.core.Response;
+import org.apache.hadoop.ozone.audit.S3GAction;
import org.apache.hadoop.ozone.s3.exception.OS3Exception;
/**
@@ -32,6 +33,16 @@
*/
abstract class BucketOperationHandler extends EndpointBase {
+ /**
+ * Returns the action for the current request if this handler is responsible
for its subresource, null otherwise.
+ * A handler that does not support all HTTP methods of its subresource
returns
+ * {@link S3GAction#NOT_IMPLEMENTED} for the unsupported ones, which the
default implementations of
+ * {@code handlePutRequest}, {@code handleGetRequest} and {@code
handleDeleteRequest} reject.
+ */
+ S3GAction getAction() {
+ return null;
+ }
+
/**
* Handle the bucket PUT operation if this handler is responsible for it.
* The handler inspects the request (query parameters, headers, etc.) to
determine
@@ -41,11 +52,11 @@ abstract class BucketOperationHandler extends EndpointBase {
* @param body the request body stream
* @return Response if this handler handles the request, null otherwise
* @throws IOException if an I/O error occurs
- * @throws OS3Exception if an S3-specific error occurs
+ * @throws OS3Exception if an S3-specific error occurs, e.g. NotImplemented
for an unsupported subresource operation
*/
Response handlePutRequest(S3RequestContext context, String bucketName,
InputStream body)
throws IOException, OS3Exception {
- return null;
+ return rejectNotImplemented(context, getAction(), bucketName);
}
/**
@@ -57,16 +68,16 @@ Response handlePutRequest(S3RequestContext context, String
bucketName, InputStre
* @param bucketName the name of the bucket
* @return Response if this handler handles the request, null otherwise
* @throws IOException if an I/O error occurs
- * @throws OS3Exception if an S3-specific error occurs
+ * @throws OS3Exception if an S3-specific error occurs, e.g. NotImplemented
for an unsupported subresource operation
*/
Response handleGetRequest(S3RequestContext context, String bucketName)
throws IOException, OS3Exception {
- return null;
+ return rejectNotImplemented(context, getAction(), bucketName);
}
Response handleDeleteRequest(S3RequestContext context, String bucketName)
throws IOException, OS3Exception {
- return null;
+ return rejectNotImplemented(context, getAction(), bucketName);
}
BucketOperationHandler copyDependenciesFrom(EndpointBase other) {
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketOperationNotImplementedHandler.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketOperationNotImplementedHandler.java
new file mode 100644
index 00000000000..0ca3cd5b413
--- /dev/null
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketOperationNotImplementedHandler.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.s3.endpoint;
+
+import com.google.common.collect.ImmutableSet;
+import java.io.InputStream;
+import java.util.Set;
+import javax.ws.rs.core.Response;
+import org.apache.hadoop.ozone.s3.util.S3Consts.QueryParams;
+
+/**
+ * Rejects operations on bucket subresources that no other handler recognizes.
+ * <p>
+ * Subresources that have a handler ({@link BucketAclHandler}, {@link
BucketGetLocationHandler},
+ * {@link BucketLifecycleHandler}, {@link BucketTaggingHandler}, {@link
ListMultipartUploadsHandler}) reject their own
+ * unsupported HTTP methods.
+ * <p>
+ * Must be added to the chain before {@link BucketCrudHandler}. Otherwise, the
requests fall through to another
+ * operation of the same HTTP method: a GET returns a {@code ListBucketResult}
body, a PUT creates the bucket and a
+ * DELETE deletes it.
+ * <p>
+ * {@code ?delete} is only valid for POST ({@code DeleteObjects}), which does
not go through this chain.
+ */
+class BucketOperationNotImplementedHandler extends BucketOperationHandler {
+
+ private static final Set<String> SUBRESOURCES = ImmutableSet.of(
+ QueryParams.ABAC, QueryParams.ACCELERATE, QueryParams.ANALYTICS,
QueryParams.CORS, QueryParams.DELETE,
+ QueryParams.ENCRYPTION, QueryParams.INTELLIGENT_TIERING,
QueryParams.INVENTORY, QueryParams.LOGGING,
+ QueryParams.METADATA_ANNOTATION_TABLE,
QueryParams.METADATA_CONFIGURATION, QueryParams.METADATA_INVENTORY_TABLE,
+ QueryParams.METADATA_JOURNAL_TABLE, QueryParams.METADATA_TABLE,
QueryParams.METRICS, QueryParams.NOTIFICATION,
+ QueryParams.OBJECT_LOCK, QueryParams.OWNERSHIP_CONTROLS,
QueryParams.POLICY, QueryParams.POLICY_STATUS,
+ QueryParams.PUBLIC_ACCESS_BLOCK, QueryParams.REPLICATION,
QueryParams.REQUEST_PAYMENT, QueryParams.SESSION,
+ QueryParams.VERSIONING, QueryParams.VERSIONS, QueryParams.WEBSITE);
+
+ @Override
+ Response handleGetRequest(S3RequestContext context, String bucketName) {
+ return rejectNotImplemented(context, SUBRESOURCES, bucketName);
+ }
+
+ @Override
+ Response handlePutRequest(S3RequestContext context, String bucketName,
InputStream body) {
+ return rejectNotImplemented(context, SUBRESOURCES, bucketName);
+ }
+
+ @Override
+ Response handleDeleteRequest(S3RequestContext context, String bucketName) {
+ return rejectNotImplemented(context, SUBRESOURCES, bucketName);
+ }
+}
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 2f89c4e4fdf..38aca6b6fe9 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
@@ -60,6 +60,7 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
+import jakarta.annotation.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
@@ -764,6 +765,30 @@ protected void validateObjectKeyUri(String keyPath) throws
OS3Exception {
}
}
+ /**
+ * Rejects the request with {@code NotImplemented} if it has any of the
{@code subresources}.
+ *
+ * @return null if the request has none of the {@code subresources}
+ */
+ Response rejectNotImplemented(S3RequestContext s3RequestContext, Set<String>
subresources, String resource) {
+ final boolean requested = subresources.stream().anyMatch(subresource ->
queryParams().get(subresource) != null);
+ return rejectNotImplemented(s3RequestContext, requested ?
S3GAction.NOT_IMPLEMENTED : null, resource);
+ }
+
+ /**
+ * Rejects the request with {@code NotImplemented} unless {@code action} is
null.
+ * The {@code action} is stored in the request context for audit logging.
+ *
+ * @param action the action determined by the handler, null if the handler
is not responsible for the request
+ * @return null if {@code action} is null
+ */
+ Response rejectNotImplemented(S3RequestContext s3RequestContext, @Nullable
S3GAction action, String resource) {
+ if (s3RequestContext.ignore(action)) {
+ return null;
+ }
+ throw newError(S3ErrorTable.NOT_IMPLEMENTED, resource);
+ }
+
protected ReplicationConfig getReplicationConfig(OzoneBucket ozoneBucket)
throws OS3Exception {
String storageType = getHeaders().getHeaderString(STORAGE_CLASS_HEADER);
String storageConfig =
getHeaders().getHeaderString(CUSTOM_METADATA_HEADER_PREFIX +
STORAGE_CONFIG_HEADER);
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 f574417d79e..0542eb99aa0 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
@@ -34,6 +34,11 @@
*/
class ListMultipartUploadsHandler extends BucketOperationHandler {
+ @Override
+ S3GAction getAction() {
+ return queryParams().get(QueryParams.UPLOADS) == null ? null :
S3GAction.NOT_IMPLEMENTED;
+ }
+
@Override
Response handleGetRequest(S3RequestContext context, String bucketName)
throws IOException, OS3Exception {
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectAclHandler.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectAclHandler.java
index 263c093970a..b203065934a 100644
---
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectAclHandler.java
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectAclHandler.java
@@ -31,6 +31,15 @@
/** Not implemented yet. */
class ObjectAclHandler extends ObjectOperationHandler {
+ @Override
+ Response handleGetRequest(ObjectRequestContext context, String keyName)
throws IOException {
+ if (context.ignore(getAction())) {
+ return null;
+ }
+
+ throw newError(NOT_IMPLEMENTED, keyName);
+ }
+
@Override
Response handlePutRequest(ObjectRequestContext context, String keyName,
InputStream body) throws IOException {
if (context.ignore(getAction())) {
@@ -45,17 +54,19 @@ Response handlePutRequest(ObjectRequestContext context,
String keyName, InputStr
}
}
- @SuppressWarnings("SwitchStatementWithTooFewBranches")
+ @Override
S3GAction getAction() {
if (queryParams().get(S3Consts.QueryParams.ACL) == null) {
return null;
}
switch (getContext().getMethod()) {
+ case HttpMethod.GET:
+ return S3GAction.GET_OBJECT_ACL;
case HttpMethod.PUT:
return S3GAction.PUT_OBJECT_ACL;
default:
- return null;
+ return S3GAction.NOT_IMPLEMENTED;
}
}
}
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectAttributesHandler.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectAttributesHandler.java
index 1a1319bc13a..171fabde56b 100644
---
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectAttributesHandler.java
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectAttributesHandler.java
@@ -101,6 +101,11 @@ static boolean hasStoredAdditionalChecksum(OzoneKey key) {
return false;
}
+ @Override
+ S3GAction getAction() {
+ return queryParams().get(QueryParams.ATTRIBUTES) == null ? null :
S3GAction.NOT_IMPLEMENTED;
+ }
+
@Override
Response handleGetRequest(ObjectRequestContext context, String keyPath)
throws IOException, OS3Exception {
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 cccbe387fe0..430300163c5 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
@@ -155,6 +155,7 @@ protected void init() {
.add(new ObjectTaggingHandler())
.add(new ObjectAttributesHandler())
.add(new MultipartKeyHandler())
+ .add(new ObjectOperationNotImplementedHandler())
.add(this)
.build();
handler = new AuditingObjectOperationHandler(chain);
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectGetTorrentHandler.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectGetTorrentHandler.java
index 312d323d803..395fbcb357d 100644
---
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectGetTorrentHandler.java
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectGetTorrentHandler.java
@@ -45,4 +45,9 @@ Response handleGetRequest(ObjectRequestContext context,
String keyName)
context.setAction(S3GAction.GET_OBJECT_TORRENT);
throw newError(NOT_IMPLEMENTED, "GetObjectTorrent");
}
+
+ @Override
+ S3GAction getAction() {
+ return queryParams().get(QueryParams.TORRENT) == null ? null :
S3GAction.NOT_IMPLEMENTED;
+ }
}
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectOperationHandler.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectOperationHandler.java
index 82ddda475c3..cbbe47f3c2a 100644
---
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectOperationHandler.java
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectOperationHandler.java
@@ -20,18 +20,29 @@
import java.io.IOException;
import java.io.InputStream;
import javax.ws.rs.core.Response;
+import org.apache.hadoop.ozone.audit.S3GAction;
import org.apache.hadoop.ozone.s3.endpoint.ObjectEndpoint.ObjectRequestContext;
import org.apache.hadoop.ozone.s3.exception.OS3Exception;
/** Interface for handling object operations using chain of responsibility
pattern. */
abstract class ObjectOperationHandler extends EndpointBase {
- Response handleDeleteRequest(ObjectRequestContext context, String keyName)
throws IOException, OS3Exception {
+ /**
+ * Returns the action for the current request if this handler is responsible
for its subresource, null otherwise.
+ * A handler that does not support all HTTP methods of its subresource
returns
+ * {@link S3GAction#NOT_IMPLEMENTED} for the unsupported ones, which the
default implementations of
+ * {@code handleDeleteRequest}, {@code handleGetRequest} and {@code
handlePutRequest} reject.
+ */
+ S3GAction getAction() {
return null;
}
+ Response handleDeleteRequest(ObjectRequestContext context, String keyName)
throws IOException, OS3Exception {
+ return rejectNotImplemented(context, getAction(), keyName);
+ }
+
Response handleGetRequest(ObjectRequestContext context, String keyName)
throws IOException, OS3Exception {
- return null;
+ return rejectNotImplemented(context, getAction(), keyName);
}
Response handleHeadRequest(ObjectRequestContext context, String keyName)
throws IOException, OS3Exception {
@@ -40,7 +51,7 @@ Response handleHeadRequest(ObjectRequestContext context,
String keyName) throws
Response handlePutRequest(ObjectRequestContext context, String keyName,
InputStream body)
throws IOException, OS3Exception {
- return null;
+ return rejectNotImplemented(context, getAction(), keyName);
}
ObjectOperationHandler copyDependenciesFrom(EndpointBase other) {
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectOperationNotImplementedHandler.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectOperationNotImplementedHandler.java
new file mode 100644
index 00000000000..afce9aca85f
--- /dev/null
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectOperationNotImplementedHandler.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.s3.endpoint;
+
+import com.google.common.collect.ImmutableSet;
+import java.io.InputStream;
+import java.util.Set;
+import javax.ws.rs.core.Response;
+import org.apache.hadoop.ozone.s3.endpoint.ObjectEndpoint.ObjectRequestContext;
+import org.apache.hadoop.ozone.s3.util.S3Consts.QueryParams;
+
+/**
+ * Rejects operations on object subresources that no other handler recognizes.
+ * <p>
+ * Subresources that have a handler ({@link ObjectAclHandler}, {@link
ObjectAttributesHandler},
+ * {@link ObjectGetTorrentHandler}, {@link ObjectTaggingHandler}) reject their
own unsupported HTTP methods.
+ * <p>
+ * Must be added to the chain before {@link ObjectEndpoint}. Otherwise, the
requests fall through to another operation
+ * of the same HTTP method: a GET returns the object body, a PUT overwrites
the object with the request body and a
+ * DELETE deletes it.
+ * <p>
+ * {@code ?uploads} is only valid for POST ({@code CreateMultipartUpload}),
which does not go through this chain.
+ */
+class ObjectOperationNotImplementedHandler extends ObjectOperationHandler {
+
+ private static final Set<String> SUBRESOURCES = ImmutableSet.of(
+ QueryParams.ANNOTATION, QueryParams.ENCRYPTION, QueryParams.LEGAL_HOLD,
QueryParams.RENAME_OBJECT,
+ QueryParams.RETENTION, QueryParams.UPLOADS);
+
+ // DeleteObject with versionId targets a specific version; ignoring it would
delete the current object instead.
+ private static final Set<String> DELETE_SUBRESOURCES =
ImmutableSet.<String>builder()
+ .addAll(SUBRESOURCES)
+ .add(QueryParams.VERSION_ID)
+ .build();
+
+ @Override
+ Response handleGetRequest(ObjectRequestContext context, String keyName) {
+ return rejectNotImplemented(context, SUBRESOURCES, keyName);
+ }
+
+ @Override
+ Response handlePutRequest(ObjectRequestContext context, String keyName,
InputStream body) {
+ return rejectNotImplemented(context, SUBRESOURCES, keyName);
+ }
+
+ @Override
+ Response handleDeleteRequest(ObjectRequestContext context, String keyName) {
+ return rejectNotImplemented(context, DELETE_SUBRESOURCES, keyName);
+ }
+}
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 c2afc0135a9..5c04df9a90d 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
@@ -46,6 +46,7 @@ Response handlePutRequest(ObjectRequestContext context,
String keyName, InputStr
}
try {
+ rejectVersionId(keyName);
S3Tagging tagging;
try {
tagging = UNMARSHALLER.get().readFrom(body);
@@ -80,6 +81,7 @@ Response handleDeleteRequest(ObjectRequestContext context,
String keyName)
return null;
}
try {
+ rejectVersionId(keyName);
getClientProtocol().deleteObjectTagging(context.getVolume().getName(),
context.getBucketName(), keyName);
getMetrics().updateDeleteObjectTaggingSuccessStats(context.getStartNanos());
return Response.noContent().build();
@@ -115,7 +117,15 @@ Response handleGetRequest(ObjectRequestContext context,
String keyName)
}
}
- private S3GAction getAction() {
+ // Tagging a specific version is not implemented; ignoring versionId would
modify the tags of the current object.
+ private void rejectVersionId(String keyName) {
+ if (queryParams().get(S3Consts.QueryParams.VERSION_ID) != null) {
+ throw S3ErrorTable.newError(S3ErrorTable.NOT_IMPLEMENTED, keyName);
+ }
+ }
+
+ @Override
+ S3GAction getAction() {
if (queryParams().get(S3Consts.QueryParams.TAGGING) == null) {
return null;
}
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3Consts.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3Consts.java
index 620d554aa5c..4f39645f62e 100644
---
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3Consts.java
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3Consts.java
@@ -182,6 +182,41 @@ public static final class QueryParams {
public static final String UPLOADS = "uploads";
public static final String LIFECYCLE = "lifecycle";
public static final String ATTRIBUTES = "attributes";
+ public static final String VERSION_ID = "versionId";
+
+ // Subresources of S3 APIs that are not implemented. They are only
recognized so that the
+ // request can be rejected with NotImplemented instead of falling through
to another operation.
+ // See BucketOperationNotImplementedHandler and
ObjectOperationNotImplementedHandler.
+ public static final String ABAC = "abac";
+ public static final String ACCELERATE = "accelerate";
+ public static final String ANALYTICS = "analytics";
+ public static final String ANNOTATION = "annotation";
+ public static final String CORS = "cors";
+ public static final String ENCRYPTION = "encryption";
+ public static final String INTELLIGENT_TIERING = "intelligent-tiering";
+ public static final String INVENTORY = "inventory";
+ public static final String LEGAL_HOLD = "legal-hold";
+ public static final String LOGGING = "logging";
+ public static final String METADATA_ANNOTATION_TABLE =
"metadataAnnotationTable";
+ public static final String METADATA_CONFIGURATION =
"metadataConfiguration";
+ public static final String METADATA_INVENTORY_TABLE =
"metadataInventoryTable";
+ public static final String METADATA_JOURNAL_TABLE = "metadataJournalTable";
+ public static final String METADATA_TABLE = "metadataTable";
+ public static final String METRICS = "metrics";
+ public static final String NOTIFICATION = "notification";
+ public static final String OBJECT_LOCK = "object-lock";
+ public static final String OWNERSHIP_CONTROLS = "ownershipControls";
+ public static final String POLICY = "policy";
+ public static final String POLICY_STATUS = "policyStatus";
+ public static final String PUBLIC_ACCESS_BLOCK = "publicAccessBlock";
+ public static final String RENAME_OBJECT = "renameObject";
+ public static final String REPLICATION = "replication";
+ public static final String REQUEST_PAYMENT = "requestPayment";
+ public static final String RETENTION = "retention";
+ public static final String SESSION = "session";
+ public static final String VERSIONING = "versioning";
+ public static final String VERSIONS = "versions";
+ public static final String WEBSITE = "website";
private QueryParams() {
// no instances
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3GActionIamMapper.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3GActionIamMapper.java
index 223b057b5bb..7615828d422 100644
---
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3GActionIamMapper.java
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3GActionIamMapper.java
@@ -75,6 +75,8 @@ private S3GActionIamMapper() {
return "PutObjectTagging";
case DELETE_OBJECT_TAGGING:
return "DeleteObjectTagging";
+ case GET_OBJECT_ACL:
+ return "GetObjectAcl";
case PUT_OBJECT_ACL:
return "PutObjectAcl";
case COPY_OBJECT:
diff --git
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestBucketNotImplemented.java
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestBucketNotImplemented.java
new file mode 100644
index 00000000000..c6c9b29a2e6
--- /dev/null
+++
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestBucketNotImplemented.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.s3.endpoint;
+
+import static
org.apache.hadoop.ozone.s3.endpoint.EndpointTestUtils.assertErrorResponse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.io.IOException;
+import java.util.stream.Stream;
+import org.apache.hadoop.ozone.client.ObjectStore;
+import org.apache.hadoop.ozone.client.OzoneClient;
+import org.apache.hadoop.ozone.client.OzoneClientStub;
+import org.apache.hadoop.ozone.om.exceptions.OMException;
+import org.apache.hadoop.ozone.s3.exception.S3ErrorTable;
+import org.apache.hadoop.ozone.s3.util.S3Consts.QueryParams;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/** Tests for bucket subresource operations that are not implemented. */
+public class TestBucketNotImplemented {
+
+ private static final String BUCKET_NAME = "b1";
+ private ObjectStore objectStore;
+ private BucketEndpoint bucketEndpoint;
+
+ @BeforeEach
+ public void setup() throws IOException {
+ final OzoneClient clientStub = new OzoneClientStub();
+ objectStore = clientStub.getObjectStore();
+ objectStore.createS3Bucket(BUCKET_NAME);
+
+ bucketEndpoint = EndpointBuilder.newBucketEndpointBuilder()
+ .setClient(clientStub)
+ .build();
+ }
+
+ /** Bucket subresources that are not implemented for any HTTP method. */
+ private static Stream<String> subresources() {
+ return Stream.of(QueryParams.ABAC, QueryParams.ACCELERATE,
QueryParams.ANALYTICS, QueryParams.CORS,
+ QueryParams.DELETE, QueryParams.ENCRYPTION,
QueryParams.INTELLIGENT_TIERING, QueryParams.INVENTORY,
+ QueryParams.LOCATION, QueryParams.LOGGING,
QueryParams.METADATA_ANNOTATION_TABLE,
+ QueryParams.METADATA_CONFIGURATION,
QueryParams.METADATA_INVENTORY_TABLE, QueryParams.METADATA_JOURNAL_TABLE,
+ QueryParams.METADATA_TABLE, QueryParams.METRICS,
QueryParams.NOTIFICATION, QueryParams.OBJECT_LOCK,
+ QueryParams.OWNERSHIP_CONTROLS, QueryParams.POLICY,
QueryParams.POLICY_STATUS, QueryParams.PUBLIC_ACCESS_BLOCK,
+ QueryParams.REPLICATION, QueryParams.REQUEST_PAYMENT,
QueryParams.SESSION, QueryParams.VERSIONING,
+ QueryParams.VERSIONS, QueryParams.WEBSITE);
+ }
+
+ /** Same as {@link #subresources()}, plus uploads, for which only GET
(ListMultipartUploads) is implemented. */
+ private static Stream<String> putSubresources() {
+ return Stream.concat(subresources(), Stream.of(QueryParams.UPLOADS));
+ }
+
+ /** Same as {@link #putSubresources()}, plus acl, for which only GET and PUT
are implemented. */
+ private static Stream<String> deleteSubresources() {
+ return Stream.concat(putSubresources(), Stream.of(QueryParams.ACL));
+ }
+
+ @ParameterizedTest
+ @MethodSource("subresources")
+ public void getIsNotImplemented(String subresource) {
+ bucketEndpoint.queryParamsForTest().set(subresource, "");
+
+ assertErrorResponse(S3ErrorTable.NOT_IMPLEMENTED, () ->
bucketEndpoint.get(BUCKET_NAME));
+ }
+
+ @ParameterizedTest
+ @MethodSource("putSubresources")
+ public void putIsNotImplementedAndDoesNotCreateBucket(String subresource) {
+ final String newBucketName = "b2";
+ bucketEndpoint.queryParamsForTest().set(subresource, "");
+
+ assertErrorResponse(S3ErrorTable.NOT_IMPLEMENTED, () ->
bucketEndpoint.put(newBucketName, null));
+ assertThrows(OMException.class, () ->
objectStore.getS3Bucket(newBucketName));
+ }
+
+ @ParameterizedTest
+ @MethodSource("deleteSubresources")
+ public void deleteIsNotImplementedAndDoesNotDeleteBucket(String subresource)
throws IOException {
+ bucketEndpoint.queryParamsForTest().set(subresource, "");
+
+ assertErrorResponse(S3ErrorTable.NOT_IMPLEMENTED, () ->
bucketEndpoint.delete(BUCKET_NAME));
+ assertNotNull(objectStore.getS3Bucket(BUCKET_NAME));
+ }
+}
diff --git
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectMultiDelete.java
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectMultiDelete.java
index f4316152ce8..a9a2a1d278d 100644
---
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectMultiDelete.java
+++
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectMultiDelete.java
@@ -21,6 +21,7 @@
import static java.util.Collections.singleton;
import static
org.apache.hadoop.ozone.s3.endpoint.EndpointTestUtils.assertErrorResponse;
import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.MALFORMED_XML;
+import static
org.apache.hadoop.ozone.s3.exception.S3ErrorTable.NOT_IMPLEMENTED;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean;
@@ -192,6 +193,29 @@ public void multiDeleteAuditsAllKeysWhenDeleteFails()
throws Exception {
assertEquals("[key1, key2, key3]", auditParams.get("failedDeletes"));
}
+ @Test
+ public void multiDeleteWithVersionIdIsNotImplemented() throws Exception {
+ final OzoneClient client = new OzoneClientStub();
+ final OzoneBucket bucket = initTestData(client);
+ final BucketEndpoint rest = EndpointBuilder.newBucketEndpointBuilder()
+ .setClient(client)
+ .build();
+
+ final DeleteObject versioned = new DeleteObject("key2");
+ versioned.setVersionId("nonexistent");
+ final MultiDeleteRequest mdr = new MultiDeleteRequest();
+ mdr.getObjects().add(new DeleteObject("key1"));
+ mdr.getObjects().add(versioned);
+
+ assertErrorResponse(NOT_IMPLEMENTED, () -> rest.multiDelete("b1", "",
mdr));
+
+ // the whole request is rejected, including the keys without VersionId
+ final Set<String> keysAtTheEnd =
Sets.newHashSet(bucket.listKeys("")).stream()
+ .map(OzoneKey::getName)
+ .collect(Collectors.toSet());
+ assertEquals(Sets.newHashSet("key1", "key2", "key3"), keysAtTheEnd);
+ }
+
private MultiDeleteRequest threeKeyRequest() {
MultiDeleteRequest mdr = new MultiDeleteRequest();
mdr.getObjects().add(new DeleteObject("key1"));
diff --git
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectNotImplemented.java
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectNotImplemented.java
new file mode 100644
index 00000000000..f4270c7a699
--- /dev/null
+++
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectNotImplemented.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.s3.endpoint;
+
+import static
org.apache.hadoop.ozone.client.OzoneClientTestUtils.assertKeyContent;
+import static
org.apache.hadoop.ozone.s3.endpoint.EndpointTestUtils.assertErrorResponse;
+import static
org.apache.hadoop.ozone.s3.endpoint.EndpointTestUtils.assertSucceeds;
+import static org.apache.hadoop.ozone.s3.endpoint.EndpointTestUtils.delete;
+import static org.apache.hadoop.ozone.s3.endpoint.EndpointTestUtils.get;
+import static org.apache.hadoop.ozone.s3.endpoint.EndpointTestUtils.put;
+import static org.apache.hadoop.ozone.s3.util.S3Consts.X_AMZ_CONTENT_SHA256;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.util.stream.Stream;
+import javax.ws.rs.core.HttpHeaders;
+import org.apache.hadoop.ozone.client.OzoneBucket;
+import org.apache.hadoop.ozone.client.OzoneClient;
+import org.apache.hadoop.ozone.client.OzoneClientStub;
+import org.apache.hadoop.ozone.s3.exception.S3ErrorTable;
+import org.apache.hadoop.ozone.s3.util.S3Consts.QueryParams;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/** Tests for object subresource operations that are not implemented. */
+public class TestObjectNotImplemented {
+
+ private static final String BUCKET_NAME = "b1";
+ private static final String KEY_NAME = "key1";
+ private static final String CONTENT = "content";
+ private OzoneBucket bucket;
+ private ObjectEndpoint objectEndpoint;
+
+ @BeforeEach
+ public void setup() throws IOException {
+ final OzoneClient clientStub = new OzoneClientStub();
+ clientStub.getObjectStore().createS3Bucket(BUCKET_NAME);
+ bucket = clientStub.getObjectStore().getS3Bucket(BUCKET_NAME);
+
+ final HttpHeaders headers = mock(HttpHeaders.class);
+
when(headers.getHeaderString(X_AMZ_CONTENT_SHA256)).thenReturn("UNSIGNED-PAYLOAD");
+
+ objectEndpoint = EndpointBuilder.newObjectEndpointBuilder()
+ .setClient(clientStub)
+ .setHeaders(headers)
+ .build();
+ assertSucceeds(() -> put(objectEndpoint, BUCKET_NAME, KEY_NAME, CONTENT));
+ }
+
+ /** Object subresources that are not implemented for PUT and DELETE. */
+ private static Stream<String> subresources() {
+ return Stream.of(QueryParams.ACL, QueryParams.ANNOTATION,
QueryParams.ATTRIBUTES, QueryParams.ENCRYPTION,
+ QueryParams.LEGAL_HOLD, QueryParams.RENAME_OBJECT,
QueryParams.RETENTION, QueryParams.TORRENT,
+ QueryParams.UPLOADS);
+ }
+
+ /** Same as {@link #subresources()}, except GetObjectAttributes, which is
implemented. */
+ private static Stream<String> getSubresources() {
+ return subresources().filter(subresource ->
!QueryParams.ATTRIBUTES.equals(subresource));
+ }
+
+ @ParameterizedTest
+ @MethodSource("getSubresources")
+ public void getIsNotImplemented(String subresource) {
+ objectEndpoint.queryParamsForTest().set(subresource, "");
+
+ assertErrorResponse(S3ErrorTable.NOT_IMPLEMENTED, () ->
get(objectEndpoint, BUCKET_NAME, KEY_NAME));
+ }
+
+ @ParameterizedTest
+ @MethodSource("subresources")
+ public void putIsNotImplementedAndDoesNotOverwriteObject(String subresource)
throws IOException {
+ objectEndpoint.queryParamsForTest().set(subresource, "");
+
+ assertErrorResponse(S3ErrorTable.NOT_IMPLEMENTED, () ->
put(objectEndpoint, BUCKET_NAME, KEY_NAME, "other"));
+ assertKeyContent(bucket, KEY_NAME, CONTENT);
+ }
+
+ @ParameterizedTest
+ @MethodSource("subresources")
+ public void deleteIsNotImplementedAndDoesNotDeleteObject(String subresource)
throws IOException {
+ objectEndpoint.queryParamsForTest().set(subresource, "");
+
+ assertErrorResponse(S3ErrorTable.NOT_IMPLEMENTED, () ->
delete(objectEndpoint, BUCKET_NAME, KEY_NAME));
+ assertKeyContent(bucket, KEY_NAME, CONTENT);
+ }
+
+ @Test
+ public void deleteWithVersionIdIsNotImplementedAndDoesNotDeleteObject()
throws IOException {
+ objectEndpoint.queryParamsForTest().set(QueryParams.VERSION_ID,
"nonexistent");
+
+ assertErrorResponse(S3ErrorTable.NOT_IMPLEMENTED, () ->
delete(objectEndpoint, BUCKET_NAME, KEY_NAME));
+ assertKeyContent(bucket, KEY_NAME, CONTENT);
+ }
+}
diff --git
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectTaggingDelete.java
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectTaggingDelete.java
index 792e193da44..a20b03569b1 100644
---
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectTaggingDelete.java
+++
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectTaggingDelete.java
@@ -36,6 +36,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import com.google.common.collect.ImmutableMap;
import java.io.IOException;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
@@ -47,6 +48,7 @@
import org.apache.hadoop.ozone.om.exceptions.OMException;
import org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes;
import org.apache.hadoop.ozone.s3.exception.OS3Exception;
+import org.apache.hadoop.ozone.s3.util.S3Consts.QueryParams;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
@@ -103,6 +105,16 @@ public void testDeleteTagging() throws IOException,
OS3Exception {
.getKey(KEY_WITH_TAG).getTags().isEmpty());
}
+ @Test
+ public void testDeleteTaggingWithVersionIdIsNotImplemented() throws
IOException {
+ rest.queryParamsForTest().set(QueryParams.VERSION_ID, "nonexistent");
+
+ assertErrorResponse(NOT_IMPLEMENTED, () -> deleteTagging(rest,
BUCKET_NAME, KEY_WITH_TAG));
+ assertEquals(
+ ImmutableMap.of("tag1", "value1", "tag2", "value2"),
+
client.getObjectStore().getS3Bucket(BUCKET_NAME).getKey(KEY_WITH_TAG).getTags());
+ }
+
@Test
public void testDeleteTaggingNoKeyFound() {
assertErrorResponse(NO_SUCH_KEY, () -> deleteTagging(rest, BUCKET_NAME,
"nonexistent"));
diff --git
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectTaggingPut.java
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectTaggingPut.java
index b09e9bee1d4..f5777a6e9fc 100644
---
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectTaggingPut.java
+++
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectTaggingPut.java
@@ -116,6 +116,14 @@ public void testPutInvalidObjectTagging() {
assertErrorResponse(MALFORMED_XML, () -> putTagging(objectEndpoint,
BUCKET_NAME, KEY_NAME, tagValueNotSpecified()));
}
+ @Test
+ public void testPutObjectTaggingWithVersionIdIsNotImplemented() throws
Exception {
+ objectEndpoint.queryParamsForTest().set(S3Consts.QueryParams.VERSION_ID,
"nonexistent");
+
+ assertErrorResponse(NOT_IMPLEMENTED, () -> putTagging(objectEndpoint,
BUCKET_NAME, KEY_NAME, twoTags()));
+
assertThat(clientStub.getObjectStore().getS3Bucket(BUCKET_NAME).getKey(KEY_NAME).getTags()).isEmpty();
+ }
+
@Test
public void testPutObjectTaggingNoKeyFound() {
assertErrorResponse(NO_SUCH_KEY, () -> putTagging(objectEndpoint,
BUCKET_NAME, "nonexistent", twoTags()));
diff --git
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/util/TestS3GActionIamMapper.java
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/util/TestS3GActionIamMapper.java
index 82f0134cffe..b2d1625a185 100644
---
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/util/TestS3GActionIamMapper.java
+++
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/util/TestS3GActionIamMapper.java
@@ -50,6 +50,7 @@ public void mapsCoreObjectActions() {
assertEquals("GetObjectTagging",
S3GActionIamMapper.toS3ActionString(S3GAction.GET_OBJECT_TAGGING));
assertEquals("PutObjectTagging",
S3GActionIamMapper.toS3ActionString(S3GAction.PUT_OBJECT_TAGGING));
assertEquals("DeleteObjectTagging",
S3GActionIamMapper.toS3ActionString(S3GAction.DELETE_OBJECT_TAGGING));
+ assertEquals("GetObjectAcl",
S3GActionIamMapper.toS3ActionString(S3GAction.GET_OBJECT_ACL));
assertEquals("PutObjectAcl",
S3GActionIamMapper.toS3ActionString(S3GAction.PUT_OBJECT_ACL));
}
@@ -64,6 +65,7 @@ public void nonIamActionsReturnNull() {
assertNull(S3GActionIamMapper.toS3ActionString(S3GAction.ASSUME_ROLE));
assertNull(S3GActionIamMapper.toS3ActionString(S3GAction.GET_CALLER_IDENTITY));
assertNull(S3GActionIamMapper.toS3ActionString(S3GAction.GENERATE_SECRET));
+ assertNull(S3GActionIamMapper.toS3ActionString(S3GAction.NOT_IMPLEMENTED));
assertNull(S3GActionIamMapper.toS3ActionString(S3GAction.REVOKE_SECRET));
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]