|
19 | 19 | import com.sun.net.httpserver.HttpHandler; |
20 | 20 |
|
21 | 21 | import org.apache.http.HttpStatus; |
| 22 | +import org.apache.logging.log4j.Level; |
22 | 23 | import org.elasticsearch.ExceptionsHelper; |
23 | 24 | import org.elasticsearch.cluster.metadata.RepositoryMetadata; |
24 | 25 | import org.elasticsearch.common.blobstore.BlobContainer; |
|
44 | 45 | import org.elasticsearch.repositories.RepositoriesMetrics; |
45 | 46 | import org.elasticsearch.repositories.blobstore.AbstractBlobContainerRetriesTestCase; |
46 | 47 | import org.elasticsearch.repositories.blobstore.BlobStoreTestUtil; |
| 48 | +import org.elasticsearch.rest.RestStatus; |
47 | 49 | import org.elasticsearch.telemetry.InstrumentType; |
48 | 50 | import org.elasticsearch.telemetry.Measurement; |
49 | 51 | import org.elasticsearch.telemetry.RecordingMeterRegistry; |
50 | 52 | import org.elasticsearch.test.ESTestCase; |
| 53 | +import org.elasticsearch.test.MockLog; |
51 | 54 | import org.elasticsearch.watcher.ResourceWatcherService; |
52 | 55 | import org.hamcrest.Matcher; |
53 | 56 | import org.junit.After; |
|
58 | 61 | import java.io.FilterInputStream; |
59 | 62 | import java.io.IOException; |
60 | 63 | import java.io.InputStream; |
| 64 | +import java.io.InputStreamReader; |
61 | 65 | import java.net.InetSocketAddress; |
62 | 66 | import java.net.SocketTimeoutException; |
63 | 67 | import java.net.UnknownHostException; |
|
72 | 76 | import java.util.concurrent.atomic.AtomicBoolean; |
73 | 77 | import java.util.concurrent.atomic.AtomicInteger; |
74 | 78 | import java.util.concurrent.atomic.AtomicLong; |
| 79 | +import java.util.regex.Pattern; |
75 | 80 |
|
76 | 81 | import static org.elasticsearch.repositories.blobstore.BlobStoreTestUtil.randomNonDataPurpose; |
77 | 82 | import static org.elasticsearch.repositories.blobstore.BlobStoreTestUtil.randomPurpose; |
@@ -806,6 +811,60 @@ public void testSuppressedDeletionErrorsAreCapped() { |
806 | 811 | assertThat(exception.getCause().getSuppressed().length, lessThan(S3BlobStore.MAX_DELETE_EXCEPTIONS)); |
807 | 812 | } |
808 | 813 |
|
| 814 | + public void testTrimmedLogAndCappedSuppressedErrorOnMultiObjectDeletionException() { |
| 815 | + final TimeValue readTimeout = TimeValue.timeValueMillis(randomIntBetween(100, 500)); |
| 816 | + int maxBulkDeleteSize = randomIntBetween(10, 30); |
| 817 | + final BlobContainer blobContainer = createBlobContainer(1, readTimeout, true, null, maxBulkDeleteSize); |
| 818 | + |
| 819 | + final Pattern pattern = Pattern.compile("<Key>(.+?)</Key>"); |
| 820 | + httpServer.createContext("/", exchange -> { |
| 821 | + if (exchange.getRequestMethod().equals("POST") && exchange.getRequestURI().toString().startsWith("/bucket/?delete")) { |
| 822 | + final String requestBody = Streams.copyToString(new InputStreamReader(exchange.getRequestBody(), StandardCharsets.UTF_8)); |
| 823 | + final var matcher = pattern.matcher(requestBody); |
| 824 | + final StringBuilder deletes = new StringBuilder(); |
| 825 | + deletes.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); |
| 826 | + deletes.append("<DeleteResult>"); |
| 827 | + while (matcher.find()) { |
| 828 | + final String key = matcher.group(1); |
| 829 | + deletes.append("<Error>"); |
| 830 | + deletes.append("<Code>").append(randomAlphaOfLength(10)).append("</Code>"); |
| 831 | + deletes.append("<Key>").append(key).append("</Key>"); |
| 832 | + deletes.append("<Message>").append(randomAlphaOfLength(40)).append("</Message>"); |
| 833 | + deletes.append("</Error>"); |
| 834 | + } |
| 835 | + deletes.append("</DeleteResult>"); |
| 836 | + |
| 837 | + byte[] response = deletes.toString().getBytes(StandardCharsets.UTF_8); |
| 838 | + exchange.getResponseHeaders().add("Content-Type", "application/xml"); |
| 839 | + exchange.sendResponseHeaders(RestStatus.OK.getStatus(), response.length); |
| 840 | + exchange.getResponseBody().write(response); |
| 841 | + exchange.close(); |
| 842 | + } else { |
| 843 | + fail("expected only deletions"); |
| 844 | + } |
| 845 | + }); |
| 846 | + var blobs = randomList(maxBulkDeleteSize, maxBulkDeleteSize, ESTestCase::randomIdentifier); |
| 847 | + try (var mockLog = MockLog.capture(S3BlobStore.class)) { |
| 848 | + mockLog.addExpectation( |
| 849 | + new MockLog.SeenEventExpectation( |
| 850 | + "deletion log", |
| 851 | + S3BlobStore.class.getCanonicalName(), |
| 852 | + Level.WARN, |
| 853 | + blobs.size() > S3BlobStore.MAX_DELETE_EXCEPTIONS |
| 854 | + ? "Failed to delete some blobs [*... (* in total, * omitted)" |
| 855 | + : "Failed to delete some blobs [*]" |
| 856 | + ) |
| 857 | + ); |
| 858 | + var exception = expectThrows( |
| 859 | + IOException.class, |
| 860 | + "deletion should not succeed", |
| 861 | + () -> blobContainer.deleteBlobsIgnoringIfNotExists(randomPurpose(), blobs.iterator()) |
| 862 | + ); |
| 863 | + assertThat(exception.getCause().getSuppressed().length, lessThan(S3BlobStore.MAX_DELETE_EXCEPTIONS)); |
| 864 | + mockLog.awaitAllExpectationsMatched(); |
| 865 | + } |
| 866 | + } |
| 867 | + |
809 | 868 | @Override |
810 | 869 | protected Matcher<Integer> getMaxRetriesMatcher(int maxRetries) { |
811 | 870 | // some attempts make meaningful progress and do not count towards the max retry limit |
|
0 commit comments