|
21 | 21 | import com.sun.net.httpserver.HttpHandler;
|
22 | 22 |
|
23 | 23 | import org.apache.http.HttpStatus;
|
| 24 | +import org.apache.logging.log4j.Level; |
24 | 25 | import org.apache.lucene.index.CorruptIndexException;
|
25 | 26 | import org.apache.lucene.store.AlreadyClosedException;
|
26 | 27 | import org.elasticsearch.ExceptionsHelper;
|
|
51 | 52 | import org.elasticsearch.repositories.RepositoriesMetrics;
|
52 | 53 | import org.elasticsearch.repositories.blobstore.AbstractBlobContainerRetriesTestCase;
|
53 | 54 | import org.elasticsearch.repositories.blobstore.BlobStoreTestUtil;
|
| 55 | +import org.elasticsearch.rest.RestStatus; |
54 | 56 | import org.elasticsearch.telemetry.InstrumentType;
|
55 | 57 | import org.elasticsearch.telemetry.Measurement;
|
56 | 58 | import org.elasticsearch.telemetry.RecordingMeterRegistry;
|
57 | 59 | import org.elasticsearch.test.ESTestCase;
|
| 60 | +import org.elasticsearch.test.MockLog; |
58 | 61 | import org.elasticsearch.watcher.ResourceWatcherService;
|
59 | 62 | import org.hamcrest.Matcher;
|
60 | 63 | import org.junit.After;
|
|
65 | 68 | import java.io.FilterInputStream;
|
66 | 69 | import java.io.IOException;
|
67 | 70 | import java.io.InputStream;
|
| 71 | +import java.io.InputStreamReader; |
68 | 72 | import java.net.InetSocketAddress;
|
69 | 73 | import java.net.SocketTimeoutException;
|
70 | 74 | import java.net.UnknownHostException;
|
|
83 | 87 | import java.util.concurrent.atomic.AtomicLong;
|
84 | 88 | import java.util.concurrent.atomic.AtomicReference;
|
85 | 89 | import java.util.function.IntConsumer;
|
| 90 | +import java.util.regex.Pattern; |
86 | 91 |
|
87 | 92 | import static org.elasticsearch.repositories.blobstore.BlobStoreTestUtil.randomNonDataPurpose;
|
88 | 93 | import static org.elasticsearch.repositories.blobstore.BlobStoreTestUtil.randomPurpose;
|
@@ -1106,6 +1111,60 @@ public void testSuppressedDeletionErrorsAreCapped() {
|
1106 | 1111 | assertThat(exception.getCause().getSuppressed().length, lessThan(S3BlobStore.MAX_DELETE_EXCEPTIONS));
|
1107 | 1112 | }
|
1108 | 1113 |
|
| 1114 | + public void testTrimmedLogAndCappedSuppressedErrorOnMultiObjectDeletionException() { |
| 1115 | + final TimeValue readTimeout = TimeValue.timeValueMillis(randomIntBetween(100, 500)); |
| 1116 | + int maxBulkDeleteSize = randomIntBetween(10, 30); |
| 1117 | + final BlobContainer blobContainer = createBlobContainer(1, readTimeout, true, null, maxBulkDeleteSize); |
| 1118 | + |
| 1119 | + final Pattern pattern = Pattern.compile("<Key>(.+?)</Key>"); |
| 1120 | + httpServer.createContext("/", exchange -> { |
| 1121 | + if (exchange.getRequestMethod().equals("POST") && exchange.getRequestURI().toString().startsWith("/bucket/?delete")) { |
| 1122 | + final String requestBody = Streams.copyToString(new InputStreamReader(exchange.getRequestBody(), StandardCharsets.UTF_8)); |
| 1123 | + final var matcher = pattern.matcher(requestBody); |
| 1124 | + final StringBuilder deletes = new StringBuilder(); |
| 1125 | + deletes.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); |
| 1126 | + deletes.append("<DeleteResult>"); |
| 1127 | + while (matcher.find()) { |
| 1128 | + final String key = matcher.group(1); |
| 1129 | + deletes.append("<Error>"); |
| 1130 | + deletes.append("<Code>").append(randomAlphaOfLength(10)).append("</Code>"); |
| 1131 | + deletes.append("<Key>").append(key).append("</Key>"); |
| 1132 | + deletes.append("<Message>").append(randomAlphaOfLength(40)).append("</Message>"); |
| 1133 | + deletes.append("</Error>"); |
| 1134 | + } |
| 1135 | + deletes.append("</DeleteResult>"); |
| 1136 | + |
| 1137 | + byte[] response = deletes.toString().getBytes(StandardCharsets.UTF_8); |
| 1138 | + exchange.getResponseHeaders().add("Content-Type", "application/xml"); |
| 1139 | + exchange.sendResponseHeaders(RestStatus.OK.getStatus(), response.length); |
| 1140 | + exchange.getResponseBody().write(response); |
| 1141 | + exchange.close(); |
| 1142 | + } else { |
| 1143 | + fail("expected only deletions"); |
| 1144 | + } |
| 1145 | + }); |
| 1146 | + var blobs = randomList(maxBulkDeleteSize, maxBulkDeleteSize, ESTestCase::randomIdentifier); |
| 1147 | + try (var mockLog = MockLog.capture(S3BlobStore.class)) { |
| 1148 | + mockLog.addExpectation( |
| 1149 | + new MockLog.SeenEventExpectation( |
| 1150 | + "deletion log", |
| 1151 | + S3BlobStore.class.getCanonicalName(), |
| 1152 | + Level.WARN, |
| 1153 | + blobs.size() > S3BlobStore.MAX_DELETE_EXCEPTIONS |
| 1154 | + ? "Failed to delete some blobs [*... (* in total, * omitted)" |
| 1155 | + : "Failed to delete some blobs [*]" |
| 1156 | + ) |
| 1157 | + ); |
| 1158 | + var exception = expectThrows( |
| 1159 | + IOException.class, |
| 1160 | + "deletion should not succeed", |
| 1161 | + () -> blobContainer.deleteBlobsIgnoringIfNotExists(randomPurpose(), blobs.iterator()) |
| 1162 | + ); |
| 1163 | + assertThat(exception.getCause().getSuppressed().length, lessThan(S3BlobStore.MAX_DELETE_EXCEPTIONS)); |
| 1164 | + mockLog.awaitAllExpectationsMatched(); |
| 1165 | + } |
| 1166 | + } |
| 1167 | + |
1109 | 1168 | @Override
|
1110 | 1169 | protected Matcher<Integer> getMaxRetriesMatcher(int maxRetries) {
|
1111 | 1170 | // some attempts make meaningful progress and do not count towards the max retry limit
|
|
0 commit comments