diff --git a/hadoop-hdds/common/src/main/resources/ozone-default.xml b/hadoop-hdds/common/src/main/resources/ozone-default.xml index f08e03c03eb..246739064bf 100644 --- a/hadoop-hdds/common/src/main/resources/ozone-default.xml +++ b/hadoop-hdds/common/src/main/resources/ozone-default.xml @@ -1757,6 +1757,14 @@ service principal. + + ozone.s3g.readonly + false + OZONE, S3GATEWAY/tag> + Whether the S3Gateway blocks PUT/POST/DELETE methods or not. + Mostly used for system maintenance. + + ozone.om.save.metrics.interval 5m diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/S3GatewayConfigKeys.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/S3GatewayConfigKeys.java index af8575300e1..56d1162c7cc 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/S3GatewayConfigKeys.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/S3GatewayConfigKeys.java @@ -64,7 +64,8 @@ public final class S3GatewayConfigKeys { public static final String OZONE_S3G_KERBEROS_PRINCIPAL_KEY = "ozone.s3g.kerberos.principal"; - + public static final String OZONE_S3G_READONLY = "ozone.s3g.readonly"; + public static final boolean OZONE_S3G_READONLY_DEFAULT = false; /** * Never constructed. */ 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 6bbd1a5b30a..3f1bc561058 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 @@ -39,6 +39,7 @@ import org.apache.hadoop.ozone.s3.util.ContinueToken; import org.apache.hadoop.ozone.s3.util.S3StorageType; import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer; + import org.apache.http.HttpStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -64,6 +65,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Optional; import java.util.Set; import static org.apache.hadoop.ozone.OzoneAcl.AclScope.ACCESS; @@ -243,6 +245,12 @@ public Response put(@PathParam("bucket") String bucketName, InputStream body) throws IOException, OS3Exception { S3GAction s3GAction = S3GAction.CREATE_BUCKET; + // Check if the S3Gateway status is readonly + Optional checkResult = checkIfReadonly(); + if (checkResult.isPresent()) { + return checkResult.get(); + } + try { if (aclMarker != null) { s3GAction = S3GAction.PUT_ACL; @@ -347,6 +355,12 @@ public Response delete(@PathParam("bucket") String bucketName) throws IOException, OS3Exception { S3GAction s3GAction = S3GAction.DELETE_BUCKET; + // Check if the S3Gateway status is readonly + Optional checkResult = checkIfReadonly(); + if (checkResult.isPresent()) { + return checkResult.get(); + } + try { deleteS3Bucket(bucketName); } catch (OMException ex) { @@ -390,9 +404,18 @@ public MultiDeleteResponse multiDelete(@PathParam("bucket") String bucketName, MultiDeleteRequest request) throws OS3Exception, IOException { S3GAction s3GAction = S3GAction.MULTI_DELETE; + MultiDeleteResponse result = new MultiDeleteResponse(); + + // Check if the S3Gateway status is readonly + Optional checkResult = checkIfReadonly(); + if (checkResult.isPresent()) { + Response res = checkResult.get(); + result.addError(new Error("", res.getStatusInfo().getReasonPhrase(), + "The S3Gateway is in read-only mode.")); + } OzoneBucket bucket = getBucket(bucketName); - MultiDeleteResponse result = new MultiDeleteResponse(); + if (request.getObjects() != null) { for (DeleteObject keyToDelete : request.getObjects()) { try { 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 56a187fd004..f06cd045941 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 @@ -24,14 +24,15 @@ import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.core.Context; import java.io.IOException; -import java.util.Set; -import java.util.HashSet; import java.util.Arrays; -import java.util.Map; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.Collections; +import java.util.Map; +import java.util.Optional; +import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -51,6 +52,7 @@ import org.apache.hadoop.ozone.om.exceptions.OMException; import org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes; import org.apache.hadoop.ozone.om.protocol.S3Auth; +import org.apache.hadoop.ozone.s3.S3GatewayConfigKeys; import org.apache.hadoop.ozone.s3.exception.OS3Exception; import org.apache.hadoop.ozone.s3.exception.S3ErrorTable; @@ -58,6 +60,7 @@ import org.apache.hadoop.ozone.s3.metrics.S3GatewayMetrics; import org.apache.hadoop.ozone.s3.util.AuditUtils; +import org.apache.http.HttpStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -380,4 +383,13 @@ protected boolean isAccessDenied(OMException ex) { || result == ResultCodes.INVALID_TOKEN; } + protected Optional checkIfReadonly() { + // Check if the S3Gateway is in read-only mode or not. + if (getClient().getConfiguration().getBoolean(S3GatewayConfigKeys.OZONE_S3G_READONLY, + S3GatewayConfigKeys.OZONE_S3G_READONLY_DEFAULT)) { + return Optional.of(Response.status(HttpStatus.SC_METHOD_NOT_ALLOWED). + header("Allow", "GET,HEAD").build()); + } + return Optional.empty(); + } } 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 488dc9ce492..78b6ac30352 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 @@ -48,9 +48,10 @@ import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; -import java.util.Map; import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.OptionalLong; import org.apache.commons.lang3.StringUtils; @@ -175,6 +176,12 @@ public Response put( @QueryParam("uploadId") @DefaultValue("") String uploadID, InputStream body) throws IOException, OS3Exception { + // Check if the S3Gateway status is readonly + Optional checkResult = checkIfReadonly(); + if (checkResult.isPresent()) { + return checkResult.get(); + } + S3GAction s3GAction = S3GAction.CREATE_KEY; boolean auditSuccess = true; @@ -527,6 +534,12 @@ public Response delete( @QueryParam("uploadId") @DefaultValue("") String uploadId) throws IOException, OS3Exception { + // Check if the S3Gateway status is readonly + Optional checkResult = checkIfReadonly(); + if (checkResult.isPresent()) { + return checkResult.get(); + } + S3GAction s3GAction = S3GAction.DELETE_KEY; try { @@ -591,6 +604,13 @@ public Response initializeMultipartUpload( @PathParam("path") String key ) throws IOException, OS3Exception { + + // Check if the S3Gateway status is readonly + Optional checkResult = checkIfReadonly(); + if (checkResult.isPresent()) { + return checkResult.get(); + } + S3GAction s3GAction = S3GAction.INIT_MULTIPART_UPLOAD; try { @@ -658,6 +678,13 @@ public Response completeMultipartUpload(@PathParam("bucket") String bucket, @QueryParam("uploadId") @DefaultValue("") String uploadID, CompleteMultipartUploadRequest multipartUploadRequest) throws IOException, OS3Exception { + + // Check if the S3Gateway status is readonly + Optional checkResult = checkIfReadonly(); + if (checkResult.isPresent()) { + return checkResult.get(); + } + S3GAction s3GAction = S3GAction.COMPLETE_MULTIPART_UPLOAD; OzoneVolume volume = getVolume(); // Using LinkedHashMap to preserve ordering of parts list.