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 17 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,6 +14,7 @@
*/
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.JacksonXmlProperty;
Expand Down Expand Up @@ -347,4 +348,122 @@ 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 "S3BatchDeleteKeys{" +
allenaverbukh marked this conversation as resolved.
Show resolved Hide resolved
"key='" + key + '\'' +
'}';
}
}

public static class S3BatchDeleteResponse {

@JacksonXmlElementWrapper(useWrapping = false) // Avoid wrapping the list in an extra element
@JacksonXmlProperty(localName = "errors") // Maps to the <errors> element in XML
private ArrayList<S3DeleteError> errors; // This should be a list of S3DeleteError objects

@JacksonXmlElementWrapper(useWrapping = false) // If you have a list of deleted keys
@JacksonXmlProperty(localName = "deleted") // Maps to the <deleted> element in XML
private List<String> deletedKeys;

// Getters and setters
public ArrayList<S3DeleteError> getErrors() {
return errors;
}

public void setErrors(ArrayList<S3DeleteError> errors) {
this.errors = errors;
}

public List<String> getDeletedKeys() {
return deletedKeys;
}

public void setDeletedKeys(List<String> deletedKeys) {
this.deletedKeys = deletedKeys;
}
}

public static class S3DeleteError {

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

@JacksonXmlProperty(localName = "code")
private int code;

// No-argument constructor for Jackson deserialization
public S3DeleteError() {
// This is required for deserialization
}

// Parameterized constructor (optional, if you want to create instances manually)
public S3DeleteError(String key, int code) {
this.key = key;
this.code = code;
}

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

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

public int getCode() {
return code;
}

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

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

}


Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ public static RequestPath parse(RestRequest restRequest, List<String> prefixesTo
* this path segment.
* @return a {@link RequestPath} object.
*/

allenaverbukh marked this conversation as resolved.
Show resolved Hide resolved
public static RequestPath parse(String path, Map<String, Object> args, List<String> prefixesToRemove,
String clusterName) throws RestServiceException {
int offset = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,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,11 @@ public static boolean isMultipartCreateUploadRequest(RestRequest restRequest) {
&& restRequest.getArgs().containsKey(UPLOADS_QUERY_PARAM);
}

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