Skip to content

Commit 2532b8c

Browse files
Handle status code 0 in S3 CMU response (#116212) (#116231)
A `CompleteMultipartUpload` action may fail after sending the `200 OK` response line. In this case the response body describes the error, and the SDK translates this situation to an exception with status code 0 but with the `ErrorCode` string set appropriately. This commit enhances the exception handling in `S3BlobContainer` to handle this possibility. Closes #102294 Co-authored-by: Pat Patterson <[email protected]>
1 parent e80a641 commit 2532b8c

File tree

4 files changed

+29
-10
lines changed

4 files changed

+29
-10
lines changed

docs/changelog/116212.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pr: 116212
2+
summary: Handle status code 0 in S3 CMU response
3+
area: Snapshot/Restore
4+
type: bug
5+
issues:
6+
- 102294

modules/repository-s3/src/main/java/org/elasticsearch/repositories/s3/S3BlobContainer.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -882,8 +882,13 @@ public void compareAndExchangeRegister(
882882
final var clientReference = blobStore.clientReference();
883883
ActionListener.run(ActionListener.releaseAfter(listener.delegateResponse((delegate, e) -> {
884884
logger.trace(() -> Strings.format("[%s]: compareAndExchangeRegister failed", key), e);
885-
if (e instanceof AmazonS3Exception amazonS3Exception && amazonS3Exception.getStatusCode() == 404) {
886-
// an uncaught 404 means that our multipart upload was aborted by a concurrent operation before we could complete it
885+
if (e instanceof AmazonS3Exception amazonS3Exception
886+
&& (amazonS3Exception.getStatusCode() == 404
887+
|| amazonS3Exception.getStatusCode() == 0 && "NoSuchUpload".equals(amazonS3Exception.getErrorCode()))) {
888+
// An uncaught 404 means that our multipart upload was aborted by a concurrent operation before we could complete it.
889+
// Also (rarely) S3 can start processing the request during a concurrent abort and this can result in a 200 OK with an
890+
// <Error><Code>NoSuchUpload</Code>... in the response, which the SDK translates to status code 0. Either way, this means
891+
// that our write encountered contention:
887892
delegate.onResponse(OptionalBytesReference.MISSING);
888893
} else {
889894
delegate.onFailure(e);

test/fixtures/s3-fixture/src/main/java/fixture/s3/S3HttpHandler.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import com.sun.net.httpserver.HttpHandler;
1414

1515
import org.elasticsearch.ExceptionsHelper;
16+
import org.elasticsearch.common.Randomness;
1617
import org.elasticsearch.common.Strings;
1718
import org.elasticsearch.common.UUIDs;
1819
import org.elasticsearch.common.bytes.BytesReference;
@@ -168,7 +169,21 @@ public void handle(final HttpExchange exchange) throws IOException {
168169
RestUtils.decodeQueryString(request, request.indexOf('?') + 1, params);
169170
final var upload = uploads.remove(params.get("uploadId"));
170171
if (upload == null) {
171-
exchange.sendResponseHeaders(RestStatus.NOT_FOUND.getStatus(), -1);
172+
if (Randomness.get().nextBoolean()) {
173+
exchange.sendResponseHeaders(RestStatus.NOT_FOUND.getStatus(), -1);
174+
} else {
175+
byte[] response = ("""
176+
<?xml version="1.0" encoding="UTF-8"?>
177+
<Error>
178+
<Code>NoSuchUpload</Code>
179+
<Message>No such upload</Message>
180+
<RequestId>test-request-id</RequestId>
181+
<HostId>test-host-id</HostId>
182+
</Error>""").getBytes(StandardCharsets.UTF_8);
183+
exchange.getResponseHeaders().add("Content-Type", "application/xml");
184+
exchange.sendResponseHeaders(RestStatus.OK.getStatus(), response.length);
185+
exchange.getResponseBody().write(response);
186+
}
172187
} else {
173188
final var blobContents = upload.complete(extractPartEtags(Streams.readFully(exchange.getRequestBody())));
174189
blobs.put(requestComponents.path, blobContents);

x-pack/plugin/snapshot-repo-test-kit/qa/s3/src/javaRestTest/java/org/elasticsearch/repositories/blobstore/testkit/analyze/S3RepositoryAnalysisRestIT.java

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,6 @@ public class S3RepositoryAnalysisRestIT extends AbstractRepositoryAnalysisRestTe
3131
.setting("s3.client.repo_test_kit.protocol", () -> "http", (n) -> USE_FIXTURE)
3232
.setting("s3.client.repo_test_kit.endpoint", s3Fixture::getAddress, (n) -> USE_FIXTURE)
3333
.setting("xpack.security.enabled", "false")
34-
// Additional tracing related to investigation into https://github.com/elastic/elasticsearch/issues/102294
35-
.setting("logger.org.elasticsearch.repositories.s3", "TRACE")
36-
.setting("logger.org.elasticsearch.repositories.blobstore.testkit", "TRACE")
37-
.setting("logger.com.amazonaws.request", "DEBUG")
38-
.setting("logger.org.apache.http.wire", "DEBUG")
39-
// Necessary to permit setting the above two restricted loggers to DEBUG
40-
.jvmArg("-Des.insecure_network_trace_enabled=true")
4134
.build();
4235

4336
@ClassRule

0 commit comments

Comments
 (0)