|
62 | 62 | import software.amazon.awssdk.services.s3.model.UploadPartRequest;
|
63 | 63 | import software.amazon.awssdk.services.s3.model.UploadPartResponse;
|
64 | 64 | import software.amazon.awssdk.services.s3.paginators.ListObjectsV2Iterable;
|
| 65 | +import software.amazon.awssdk.services.s3.paginators.ListObjectsV2Publisher; |
65 | 66 | import software.amazon.awssdk.utils.CollectionUtils;
|
66 | 67 |
|
67 | 68 | import org.apache.logging.log4j.LogManager;
|
|
90 | 91 | import org.opensearch.core.common.Strings;
|
91 | 92 | import org.opensearch.core.common.unit.ByteSizeUnit;
|
92 | 93 | import org.opensearch.core.common.unit.ByteSizeValue;
|
| 94 | +import org.opensearch.repositories.s3.async.S3AsyncDeleteHelper; |
93 | 95 | import org.opensearch.repositories.s3.async.SizeBasedBlockingQ;
|
94 | 96 | import org.opensearch.repositories.s3.async.UploadRequest;
|
95 | 97 | import org.opensearch.repositories.s3.utils.HttpRangeUtils;
|
|
109 | 111 | import java.util.function.Function;
|
110 | 112 | import java.util.stream.Collectors;
|
111 | 113 |
|
| 114 | +import org.reactivestreams.Subscriber; |
| 115 | +import org.reactivestreams.Subscription; |
| 116 | + |
112 | 117 | import static org.opensearch.repositories.s3.S3Repository.MAX_FILE_SIZE;
|
113 | 118 | import static org.opensearch.repositories.s3.S3Repository.MAX_FILE_SIZE_USING_MULTIPART;
|
114 | 119 | import static org.opensearch.repositories.s3.S3Repository.MIN_PART_SIZE_USING_MULTIPART;
|
@@ -875,4 +880,123 @@ CompletableFuture<GetObjectAttributesResponse> getBlobMetadata(S3AsyncClient s3A
|
875 | 880 |
|
876 | 881 | return SocketAccess.doPrivileged(() -> s3AsyncClient.getObjectAttributes(getObjectAttributesRequest));
|
877 | 882 | }
|
| 883 | + |
| 884 | + @Override |
| 885 | + public void deleteAsync(ActionListener<DeleteResult> completionListener) { |
| 886 | + try (AmazonAsyncS3Reference asyncClientReference = blobStore.asyncClientReference()) { |
| 887 | + S3AsyncClient s3AsyncClient = asyncClientReference.get().client(); |
| 888 | + |
| 889 | + ListObjectsV2Request listRequest = ListObjectsV2Request.builder().bucket(blobStore.bucket()).prefix(keyPath).build(); |
| 890 | + ListObjectsV2Publisher listPublisher = s3AsyncClient.listObjectsV2Paginator(listRequest); |
| 891 | + |
| 892 | + AtomicLong deletedBlobs = new AtomicLong(); |
| 893 | + AtomicLong deletedBytes = new AtomicLong(); |
| 894 | + |
| 895 | + CompletableFuture<Void> listingFuture = new CompletableFuture<>(); |
| 896 | + |
| 897 | + listPublisher.subscribe(new Subscriber<>() { |
| 898 | + private Subscription subscription; |
| 899 | + private final List<String> objectsToDelete = new ArrayList<>(); |
| 900 | + private CompletableFuture<Void> deletionChain = CompletableFuture.completedFuture(null); |
| 901 | + |
| 902 | + @Override |
| 903 | + public void onSubscribe(Subscription s) { |
| 904 | + this.subscription = s; |
| 905 | + subscription.request(1); |
| 906 | + } |
| 907 | + |
| 908 | + @Override |
| 909 | + public void onNext(ListObjectsV2Response response) { |
| 910 | + response.contents().forEach(s3Object -> { |
| 911 | + deletedBlobs.incrementAndGet(); |
| 912 | + deletedBytes.addAndGet(s3Object.size()); |
| 913 | + objectsToDelete.add(s3Object.key()); |
| 914 | + }); |
| 915 | + |
| 916 | + int bulkDeleteSize = blobStore.getBulkDeletesSize(); |
| 917 | + if (objectsToDelete.size() >= bulkDeleteSize) { |
| 918 | + int fullBatchesCount = objectsToDelete.size() / bulkDeleteSize; |
| 919 | + int itemsToDelete = fullBatchesCount * bulkDeleteSize; |
| 920 | + |
| 921 | + List<String> batchToDelete = new ArrayList<>(objectsToDelete.subList(0, itemsToDelete)); |
| 922 | + objectsToDelete.subList(0, itemsToDelete).clear(); |
| 923 | + |
| 924 | + deletionChain = S3AsyncDeleteHelper.executeDeleteChain( |
| 925 | + s3AsyncClient, |
| 926 | + blobStore, |
| 927 | + batchToDelete, |
| 928 | + deletionChain, |
| 929 | + () -> subscription.request(1) |
| 930 | + ); |
| 931 | + } else { |
| 932 | + subscription.request(1); |
| 933 | + } |
| 934 | + } |
| 935 | + |
| 936 | + @Override |
| 937 | + public void onError(Throwable t) { |
| 938 | + listingFuture.completeExceptionally(new IOException("Failed to list objects for deletion", t)); |
| 939 | + } |
| 940 | + |
| 941 | + @Override |
| 942 | + public void onComplete() { |
| 943 | + if (!objectsToDelete.isEmpty()) { |
| 944 | + deletionChain = S3AsyncDeleteHelper.executeDeleteChain( |
| 945 | + s3AsyncClient, |
| 946 | + blobStore, |
| 947 | + objectsToDelete, |
| 948 | + deletionChain, |
| 949 | + null |
| 950 | + ); |
| 951 | + } |
| 952 | + deletionChain.whenComplete((v, throwable) -> { |
| 953 | + if (throwable != null) { |
| 954 | + listingFuture.completeExceptionally(throwable); |
| 955 | + } else { |
| 956 | + listingFuture.complete(null); |
| 957 | + } |
| 958 | + }); |
| 959 | + } |
| 960 | + }); |
| 961 | + |
| 962 | + listingFuture.whenComplete((v, throwable) -> { |
| 963 | + if (throwable != null) { |
| 964 | + completionListener.onFailure( |
| 965 | + throwable instanceof Exception |
| 966 | + ? (Exception) throwable |
| 967 | + : new IOException("Unexpected error during async deletion", throwable) |
| 968 | + ); |
| 969 | + } else { |
| 970 | + completionListener.onResponse(new DeleteResult(deletedBlobs.get(), deletedBytes.get())); |
| 971 | + } |
| 972 | + }); |
| 973 | + } catch (Exception e) { |
| 974 | + completionListener.onFailure(new IOException("Failed to initiate async deletion", e)); |
| 975 | + } |
| 976 | + } |
| 977 | + |
| 978 | + @Override |
| 979 | + public void deleteBlobsAsyncIgnoringIfNotExists(List<String> blobNames, ActionListener<Void> completionListener) { |
| 980 | + if (blobNames.isEmpty()) { |
| 981 | + completionListener.onResponse(null); |
| 982 | + return; |
| 983 | + } |
| 984 | + |
| 985 | + try (AmazonAsyncS3Reference asyncClientReference = blobStore.asyncClientReference()) { |
| 986 | + S3AsyncClient s3AsyncClient = asyncClientReference.get().client(); |
| 987 | + |
| 988 | + List<String> keysToDelete = blobNames.stream().map(this::buildKey).collect(Collectors.toList()); |
| 989 | + |
| 990 | + S3AsyncDeleteHelper.executeDeleteChain(s3AsyncClient, blobStore, keysToDelete, CompletableFuture.completedFuture(null), null) |
| 991 | + .whenComplete((v, throwable) -> { |
| 992 | + if (throwable != null) { |
| 993 | + completionListener.onFailure(new IOException("Failed to delete blobs " + blobNames, throwable)); |
| 994 | + } else { |
| 995 | + completionListener.onResponse(null); |
| 996 | + } |
| 997 | + }); |
| 998 | + } catch (Exception e) { |
| 999 | + completionListener.onFailure(new IOException("Failed to initiate async blob deletion", e)); |
| 1000 | + } |
| 1001 | + } |
878 | 1002 | }
|
0 commit comments