|
69 | 69 | import software.amazon.awssdk.services.s3.model.UploadPartRequest; |
70 | 70 | import software.amazon.awssdk.services.s3.model.UploadPartResponse; |
71 | 71 | import software.amazon.awssdk.services.s3.paginators.ListObjectsV2Iterable; |
| 72 | +import software.amazon.awssdk.services.s3.paginators.ListObjectsV2Publisher; |
72 | 73 |
|
73 | 74 | import org.opensearch.action.LatchedActionListener; |
74 | 75 | import org.opensearch.common.blobstore.BlobContainer; |
|
101 | 102 | import java.util.concurrent.CompletableFuture; |
102 | 103 | import java.util.concurrent.CountDownLatch; |
103 | 104 | import java.util.concurrent.atomic.AtomicInteger; |
| 105 | +import java.util.concurrent.atomic.AtomicReference; |
104 | 106 | import java.util.stream.Collectors; |
105 | 107 | import java.util.stream.IntStream; |
106 | 108 |
|
107 | 109 | import org.mockito.ArgumentCaptor; |
108 | 110 | import org.mockito.ArgumentMatchers; |
| 111 | +import org.reactivestreams.Subscriber; |
| 112 | +import org.reactivestreams.Subscription; |
109 | 113 |
|
110 | 114 | import static org.hamcrest.Matchers.equalTo; |
111 | 115 | import static org.hamcrest.Matchers.instanceOf; |
| 116 | +import static org.mockito.ArgumentMatchers.any; |
112 | 117 | import static org.mockito.ArgumentMatchers.eq; |
113 | | -import static org.mockito.Mockito.any; |
114 | 118 | import static org.mockito.Mockito.doAnswer; |
115 | 119 | import static org.mockito.Mockito.mock; |
116 | 120 | import static org.mockito.Mockito.times; |
@@ -1275,6 +1279,94 @@ public void testTransformResponseToInputStreamContainer() throws Exception { |
1275 | 1279 | assertEquals(inputStream.available(), inputStreamContainer.getInputStream().available()); |
1276 | 1280 | } |
1277 | 1281 |
|
| 1282 | + public void testDeleteAsync() throws Exception { |
| 1283 | + final String bucketName = randomAlphaOfLengthBetween(1, 10); |
| 1284 | + final BlobPath blobPath = new BlobPath(); |
| 1285 | + |
| 1286 | + final S3BlobStore blobStore = mock(S3BlobStore.class); |
| 1287 | + when(blobStore.bucket()).thenReturn(bucketName); |
| 1288 | + when(blobStore.getStatsMetricPublisher()).thenReturn(new StatsMetricPublisher()); |
| 1289 | + when(blobStore.getBulkDeletesSize()).thenReturn(1); |
| 1290 | + |
| 1291 | + final S3AsyncClient s3AsyncClient = mock(S3AsyncClient.class); |
| 1292 | + final AmazonAsyncS3Reference asyncClientReference = mock(AmazonAsyncS3Reference.class); |
| 1293 | + when(blobStore.asyncClientReference()).thenReturn(asyncClientReference); |
| 1294 | + AmazonAsyncS3WithCredentials amazonAsyncS3WithCredentials = AmazonAsyncS3WithCredentials.create( |
| 1295 | + s3AsyncClient, |
| 1296 | + s3AsyncClient, |
| 1297 | + s3AsyncClient, |
| 1298 | + null |
| 1299 | + ); |
| 1300 | + when(asyncClientReference.get()).thenReturn(amazonAsyncS3WithCredentials); |
| 1301 | + |
| 1302 | + final List<S3Object> s3Objects = Arrays.asList( |
| 1303 | + S3Object.builder().key("key1").size(100L).build(), |
| 1304 | + S3Object.builder().key("key2").size(200L).build(), |
| 1305 | + S3Object.builder().key("key3").size(300L).build() |
| 1306 | + ); |
| 1307 | + |
| 1308 | + final ListObjectsV2Response response1 = ListObjectsV2Response.builder().contents(s3Objects.subList(0, 2)).build(); |
| 1309 | + final ListObjectsV2Response response2 = ListObjectsV2Response.builder().contents(s3Objects.subList(2, 3)).build(); |
| 1310 | + |
| 1311 | + final ListObjectsV2Publisher listPublisher = mock(ListObjectsV2Publisher.class); |
| 1312 | + AtomicInteger counter = new AtomicInteger(); |
| 1313 | + doAnswer(invocation -> { |
| 1314 | + Subscriber<ListObjectsV2Response> subscriber = invocation.getArgument(0); |
| 1315 | + subscriber.onSubscribe(new Subscription() { |
| 1316 | + @Override |
| 1317 | + public void request(long n) { |
| 1318 | + int currentCounter = counter.getAndIncrement(); |
| 1319 | + if (currentCounter == 0) { |
| 1320 | + subscriber.onNext(response1); |
| 1321 | + } |
| 1322 | + if (currentCounter == 1) { |
| 1323 | + subscriber.onNext(response2); |
| 1324 | + } |
| 1325 | + if (currentCounter == 2) { |
| 1326 | + subscriber.onComplete(); |
| 1327 | + } |
| 1328 | + } |
| 1329 | + |
| 1330 | + @Override |
| 1331 | + public void cancel() {} |
| 1332 | + }); |
| 1333 | + return null; |
| 1334 | + }).when(listPublisher).subscribe(any(Subscriber.class)); |
| 1335 | + |
| 1336 | + when(s3AsyncClient.listObjectsV2Paginator(any(ListObjectsV2Request.class))).thenReturn(listPublisher); |
| 1337 | + |
| 1338 | + when(s3AsyncClient.deleteObjects(any(DeleteObjectsRequest.class))).thenReturn( |
| 1339 | + CompletableFuture.completedFuture(DeleteObjectsResponse.builder().build()) |
| 1340 | + ); |
| 1341 | + |
| 1342 | + final S3BlobContainer blobContainer = new S3BlobContainer(blobPath, blobStore); |
| 1343 | + |
| 1344 | + CountDownLatch latch = new CountDownLatch(1); |
| 1345 | + AtomicReference<DeleteResult> deleteResultRef = new AtomicReference<>(); |
| 1346 | + blobContainer.deleteAsync(new ActionListener<>() { |
| 1347 | + @Override |
| 1348 | + public void onResponse(DeleteResult deleteResult) { |
| 1349 | + deleteResultRef.set(deleteResult); |
| 1350 | + latch.countDown(); |
| 1351 | + } |
| 1352 | + |
| 1353 | + @Override |
| 1354 | + public void onFailure(Exception e) { |
| 1355 | + logger.error("exception during deleteAsync", e); |
| 1356 | + fail("Unexpected failure: " + e.getMessage()); |
| 1357 | + } |
| 1358 | + }); |
| 1359 | + |
| 1360 | + latch.await(); |
| 1361 | + |
| 1362 | + DeleteResult deleteResult = deleteResultRef.get(); |
| 1363 | + assertEquals(3, deleteResult.blobsDeleted()); |
| 1364 | + assertEquals(600, deleteResult.bytesDeleted()); |
| 1365 | + |
| 1366 | + verify(s3AsyncClient, times(1)).listObjectsV2Paginator(any(ListObjectsV2Request.class)); |
| 1367 | + verify(s3AsyncClient, times(3)).deleteObjects(any(DeleteObjectsRequest.class)); |
| 1368 | + } |
| 1369 | + |
1278 | 1370 | private void mockObjectResponse(S3AsyncClient s3AsyncClient, String bucketName, String blobName, int objectSize) { |
1279 | 1371 |
|
1280 | 1372 | final InputStream inputStream = new ByteArrayInputStream(randomByteArrayOfLength(objectSize)); |
|
0 commit comments