Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable S3BatchDelete #2965

Merged
merged 27 commits into from
Jan 31, 2025
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
6ac91f2
[WIP] s3BatchDelete
snalli Dec 13, 2024
e9ea4a1
initial method for parsing objects from request
allenaverbukh Dec 19, 2024
5b0bf21
attempted to formulate new path for newrequest of single delete
allenaverbukh Dec 20, 2024
aa43702
current version
allenaverbukh Jan 3, 2025
a268efa
moved code to callback
allenaverbukh Jan 3, 2025
2280b8c
populated response via xmlmapper, exception handling
allenaverbukh Jan 14, 2025
a91a723
added latest
allenaverbukh Jan 15, 2025
6a51203
addressed comments from latest PR
allenaverbukh Jan 17, 2025
5716b02
UT
allenaverbukh Jan 21, 2025
0b8de6e
modified xml input
allenaverbukh Jan 22, 2025
05de8f8
block at very end
allenaverbukh Jan 23, 2025
5cd2258
changed response XML and updated UT
allenaverbukh Jan 27, 2025
3b33691
modified code to be of type int
allenaverbukh Jan 28, 2025
782894c
removed todo
allenaverbukh Jan 28, 2025
86bea0a
addressed pr comments
allenaverbukh Jan 28, 2025
7741411
final cleanup
allenaverbukh Jan 28, 2025
8399cf0
formatting
allenaverbukh Jan 28, 2025
ba6665f
refined final comments
allenaverbukh Jan 29, 2025
346853b
removed s from keys
allenaverbukh Jan 29, 2025
d6fe619
removed extra space
allenaverbukh Jan 29, 2025
21935d9
deleted extra file
allenaverbukh Jan 30, 2025
c1446db
added constant to correct file
allenaverbukh Jan 30, 2025
dec9426
changed xml to be nonconcurrent
allenaverbukh Jan 30, 2025
f26fb82
removed all extra logger statements
allenaverbukh Jan 30, 2025
d1a38f4
address more comments
allenaverbukh Jan 30, 2025
a6f1d10
updated tests
allenaverbukh Jan 30, 2025
ea23ccc
final modifications
allenaverbukh Jan 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ public static NamedBlobPath parseS3(String path, Map<String, Object> args) throw
path = path.startsWith("/") ? path.substring(1) : path;
String[] splitPath = path.split("/", 4);
String blobNamePrefix = RestUtils.getHeader(args, PREFIX_PARAM, false);
boolean isBatchDelete = args.containsKey(BATCH_DELETE_QUERY_PARAM);
boolean isGetObjectLockRequest = args.containsKey(OBJECT_LOCK_PARAM);
//There are two cases for S3 listing
//1.has prefix (Ex:GET /?prefix=prefixName&delimiter=&encoding-type=url)
Expand All @@ -118,7 +119,7 @@ public static NamedBlobPath parseS3(String path, Map<String, Object> args) throw
}
return new NamedBlobPath(accountName, containerName, null, blobNamePrefix, pageToken);
}
if (isGetObjectLockRequest) {
if (isGetObjectLockRequest || isBatchDelete) {
return new NamedBlobPath(accountName, containerName, null, null, null);
} else {
String blobName = splitPath[3];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@
*/
package com.github.ambry.frontend.s3;

import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import java.util.concurrent.ConcurrentLinkedQueue;


/**
Expand Down Expand Up @@ -351,4 +354,143 @@ public String toString() {
return "Bucket=" + bucket + ", Key=" + key + ", UploadId=" + uploadId;
}
}

public static class S3BatchDeleteObjects {

// Ensure that the "Delete" wrapper element is mapped correctly to the list of "Object" elements
@JacksonXmlElementWrapper(useWrapping = false) // Avoids wrapping the <Delete> element itself
@JacksonXmlProperty(localName = "Object") // Specifies that each <Object> element maps to an instance of S3BatchDeleteKeys
private List<S3BatchDeleteKey> objects;

public List<S3BatchDeleteKey> getObjects() {
return objects;
}

public void setObjects(List<S3BatchDeleteKey> objects) {
this.objects = objects;
}

@Override
public String toString() {
return "S3BatchDeleteObjects{" +
"objects=" + objects +
'}';
}
}

public static class S3BatchDeleteKey {

// Maps the <Key> element inside each <Object> to the 'key' property in S3BatchDeleteKeys
@JacksonXmlProperty(localName = "Key")
private String key;

public String getKey() {
return key;
}

public void setKey(String key) {
this.key = key;
}

@Override
public String toString() {
return "S3BatchDeleteKey{" +
"key='" + key + '\'' +
'}';
}
}

// exact naming from S3BatchDelete response
public static class DeleteResult {

@JacksonXmlElementWrapper(useWrapping = false)
@JacksonXmlProperty(localName = "Error") // Maps to <Error> in XML
private List<S3MessagePayload.S3ErrorObject> errors;

@JacksonXmlElementWrapper(useWrapping = false)
@JacksonXmlProperty(localName = "Deleted") // Maps to <Deleted> in XML
private List<S3MessagePayload.S3DeletedObject> deleted;

// Getters and setters
public List<S3MessagePayload.S3ErrorObject> getErrors() {
return errors;
}

public void setErrors(List<S3MessagePayload.S3ErrorObject> errors) {
this.errors = errors;
}

public List<S3MessagePayload.S3DeletedObject> getDeleted() {
return deleted;
}

public void setDeleted(List<S3MessagePayload.S3DeletedObject> deleted) {
this.deleted = deleted;
}
}

public static class S3ErrorObject {

@JacksonXmlProperty(localName = "Key")
private String key;

@JacksonXmlProperty(localName = "Code")
private String code;

public S3ErrorObject(){}

public S3ErrorObject(String key, String code) {
this.key = key;
this.code = code;
}

// Getters and setters
public String getKey() {
return key;
}

public void setKey(String key) {
this.key = key;
}

public String getCode() {
return code;
}

public void setCode(String code) {
this.code = code;
}

@Override
public String toString() {
return "S3ErrorObject{key='" + key + "', code='" + code + "'}";
}
}

public static class S3DeletedObject {

@JacksonXmlProperty(localName = "Key")
private String key;

public S3DeletedObject() {}

public S3DeletedObject(String key) {
this.key = key;
}

// Getters and setters
public String getKey() {
return key;
}

public void setKey(String key) {
this.key = key;
}

@Override
public String toString() {
return "S3DeletedObject{key='" + key + "'}";
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public class RestUtils {
public static final String PATH_SEPARATOR_STRING = "/";
public static final String STITCH = "STITCH";
public static final String UPLOADS_QUERY_PARAM = "uploads";
public static final String BATCH_DELETE_QUERY_PARAM = "delete";
public static final String UPLOAD_ID_QUERY_PARAM = "uploadId";
public static final String CONTINUE = "100-continue";
public static final String OBJECT_LOCK_PARAM = "object-lock";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricRegistry;
import com.github.ambry.config.FrontendConfig;
import com.github.ambry.frontend.s3.S3BatchDeleteHandler;
import com.github.ambry.frontend.s3.S3DeleteHandler;
import com.github.ambry.frontend.s3.S3GetHandler;
import com.github.ambry.frontend.s3.S3ListHandler;
Expand All @@ -35,6 +36,7 @@ public class FrontendMetrics {
// RestRequestMetricsGroup
// DELETE
public final RestRequestMetricsGroup deleteBlobMetricsGroup;
public final RestRequestMetricsGroup batchDeleteMetricsGroup;
public final RestRequestMetricsGroup deleteDatasetsMetricsGroup;
//COPY
public final RestRequestMetricsGroup copyBlobMetricsGroup;
Expand Down Expand Up @@ -165,6 +167,7 @@ public class FrontendMetrics {
public final AsyncOperationTracker.Metrics deleteDatasetOutOfRetentionRequestMetrics;

public final AsyncOperationTracker.Metrics s3DeleteHandleMetrics;
public final AsyncOperationTracker.Metrics s3BatchDeleteHandleMetrics;
public final AsyncOperationTracker.Metrics s3ListHandleMetrics;
public final AsyncOperationTracker.Metrics s3PutHandleMetrics;
public final AsyncOperationTracker.Metrics s3GetHandleMetrics;
Expand Down Expand Up @@ -315,6 +318,9 @@ public FrontendMetrics(MetricRegistry metricRegistry, FrontendConfig frontendCon
deleteBlobMetricsGroup =
new RestRequestMetricsGroup(FrontendRestRequestService.class, "DeleteBlob", false, metricRegistry,
frontendConfig);
batchDeleteMetricsGroup =
new RestRequestMetricsGroup(FrontendRestRequestService.class, "BatchDeleteBlob", false, metricRegistry,
frontendConfig);
deleteDatasetsMetricsGroup =
new RestRequestMetricsGroup(FrontendRestRequestService.class, "DeleteDataset", false, metricRegistry,
frontendConfig);
Expand Down Expand Up @@ -505,6 +511,7 @@ public FrontendMetrics(MetricRegistry metricRegistry, FrontendConfig frontendCon
new AsyncOperationTracker.Metrics(NamedBlobPutHandler.class, "RetentionRequest", metricRegistry);

s3DeleteHandleMetrics = new AsyncOperationTracker.Metrics(S3DeleteHandler.class, "S3Handle", metricRegistry);
s3BatchDeleteHandleMetrics = new AsyncOperationTracker.Metrics(S3BatchDeleteHandler.class, "S3Handle", metricRegistry);
s3ListHandleMetrics = new AsyncOperationTracker.Metrics(S3ListHandler.class, "S3Handle", metricRegistry);
s3PutHandleMetrics = new AsyncOperationTracker.Metrics(S3PutHandler.class, "S3Handle", metricRegistry);
s3GetHandleMetrics = new AsyncOperationTracker.Metrics(S3GetHandler.class, "S3Handle", metricRegistry);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import com.github.ambry.clustermap.ClusterMap;
import com.github.ambry.commons.Callback;
import com.github.ambry.config.FrontendConfig;
import com.github.ambry.frontend.s3.S3BatchDeleteHandler;
import com.github.ambry.frontend.s3.S3DeleteHandler;
import com.github.ambry.frontend.s3.S3GetHandler;
import com.github.ambry.frontend.s3.S3HeadHandler;
Expand Down Expand Up @@ -113,6 +114,7 @@ class FrontendRestRequestService implements RestRequestService {
private PostDatasetsHandler postDatasetsHandler;
private GetStatsReportHandler getStatsReportHandler;
private S3DeleteHandler s3DeleteHandler;
private S3BatchDeleteHandler s3BatchDeleteHandler;
private S3ListHandler s3ListHandler;
private S3PutHandler s3PutHandler;
private S3HeadHandler s3HeadHandler;
Expand Down Expand Up @@ -240,7 +242,8 @@ public void start() throws InstantiationException {
new S3MultipartUploadHandler(securityService, frontendMetrics, accountAndContainerInjector, frontendConfig,
namedBlobDb, idConverter, router, quotaManager);
s3DeleteHandler = new S3DeleteHandler(deleteBlobHandler, s3MultipartUploadHandler, frontendMetrics);
s3PostHandler = new S3PostHandler(s3MultipartUploadHandler);
s3BatchDeleteHandler = new S3BatchDeleteHandler(deleteBlobHandler, frontendMetrics);
s3PostHandler = new S3PostHandler(s3MultipartUploadHandler, s3BatchDeleteHandler);
s3PutHandler = new S3PutHandler(namedBlobPutHandler, s3MultipartUploadHandler, frontendMetrics);
s3ListHandler = new S3ListHandler(namedBlobListHandler, frontendMetrics);
s3GetHandler =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
* require a response body, otherwise it is {@link ReadableStreamChannel}.
*/
abstract public class S3BaseHandler<R> {
private static final Logger LOGGER = LoggerFactory.getLogger(S3BaseHandler.class);
protected static final Logger LOGGER = LoggerFactory.getLogger(S3BaseHandler.class);

/**
* Handles the S3 request and construct the response.
Expand Down Expand Up @@ -122,6 +122,15 @@ public static boolean isMultipartCreateUploadRequest(RestRequest restRequest) {
&& restRequest.getArgs().containsKey(UPLOADS_QUERY_PARAM);
}

/**
* @param restRequest the {@link RestRequest} that contains the request parameters.
* @return {@code True} if it is a request for batch delete.
*/
public static boolean isBatchDelete(RestRequest restRequest) {
allenaverbukh marked this conversation as resolved.
Show resolved Hide resolved
return restRequest.getRestMethod() == RestMethod.POST && restRequest.getArgs().containsKey(S3_REQUEST)
&& restRequest.getArgs().containsKey(BATCH_DELETE_QUERY_PARAM);
}

/**
* @param restRequest the {@link RestRequest} that contains the request parameters.
* @return {@code True} if it is a completion/abortion of multipart uploads.
Expand Down
Loading
Loading