Skip to content

Commit 8d54069

Browse files
authored
Support ListObjectsV2 in S3HttpHandler (elastic#126189) (elastic#126268)
`ListObjects` and `ListObjectsV2` only really differ in their approach to pagination, but today `S3HttpHandler` does not simulate pagination anyway so we can use the same handling code for both APIs. The only practical difference is that the v2 SDK requires the `<IsTruncated>` element in a `ListObjectsV2` response, but this element is permitted in both APIs so we add it here.
1 parent a7d52cb commit 8d54069

File tree

2 files changed

+54
-35
lines changed

2 files changed

+54
-35
lines changed

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -207,10 +207,6 @@ public void handle(final HttpExchange exchange) throws IOException {
207207
exchange.sendResponseHeaders(RestStatus.OK.getStatus(), -1);
208208

209209
} else if (request.isListObjectsRequest()) {
210-
if (request.queryParameters().containsKey("list-type")) {
211-
throw new AssertionError("Test must be adapted for GET Bucket (List Objects) Version 2");
212-
}
213-
214210
final StringBuilder list = new StringBuilder();
215211
list.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
216212
list.append("<ListBucketResult>");
@@ -223,6 +219,9 @@ public void handle(final HttpExchange exchange) throws IOException {
223219
if (delimiter != null) {
224220
list.append("<Delimiter>").append(delimiter).append("</Delimiter>");
225221
}
222+
// Would be good to test pagination here (the only real difference between ListObjects and ListObjectsV2) but for now
223+
// we return all the results at once.
224+
list.append("<IsTruncated>false</IsTruncated>");
226225
for (Map.Entry<String, BytesReference> blob : blobs.entrySet()) {
227226
if (prefix != null && blob.getKey().startsWith("/" + bucket + "/" + prefix) == false) {
228227
continue;

test/fixtures/s3-fixture/src/test/java/fixture/s3/S3HttpHandlerTests.java

Lines changed: 51 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import com.sun.net.httpserver.HttpExchange;
1515
import com.sun.net.httpserver.HttpPrincipal;
1616

17+
import org.elasticsearch.common.Randomness;
1718
import org.elasticsearch.common.Strings;
1819
import org.elasticsearch.common.bytes.BytesArray;
1920
import org.elasticsearch.common.bytes.BytesReference;
@@ -28,6 +29,7 @@
2829
import java.net.InetSocketAddress;
2930
import java.net.URI;
3031
import java.nio.charset.StandardCharsets;
32+
import java.util.ArrayList;
3133
import java.util.List;
3234
import java.util.Objects;
3335

@@ -45,63 +47,81 @@ public void testRejectsBadUri() {
4547
);
4648
}
4749

50+
private static void assertListObjectsResponse(
51+
S3HttpHandler handler,
52+
@Nullable String prefix,
53+
@Nullable String delimiter,
54+
String expectedResponse
55+
) {
56+
final var queryParts = new ArrayList<String>(3);
57+
if (prefix != null) {
58+
queryParts.add("prefix=" + prefix);
59+
}
60+
if (delimiter != null) {
61+
queryParts.add("delimiter=" + delimiter);
62+
}
63+
if (randomBoolean()) {
64+
// test both ListObjects and ListObjectsV2 - they only differ in terms of pagination but S3HttpHandler doesn't do that
65+
queryParts.add("list-type=2");
66+
}
67+
Randomness.shuffle(queryParts);
68+
69+
final var requestUri = "/bucket" + randomFrom("", "/") + (queryParts.isEmpty() ? "" : "?" + String.join("&", queryParts));
70+
assertEquals("GET " + requestUri, new TestHttpResponse(RestStatus.OK, expectedResponse), handleRequest(handler, "GET", requestUri));
71+
}
72+
4873
public void testSimpleObjectOperations() {
4974
final var handler = new S3HttpHandler("bucket", "path");
5075

5176
assertEquals(RestStatus.NOT_FOUND, handleRequest(handler, "GET", "/bucket/path/blob").status());
5277

53-
assertEquals(
54-
new TestHttpResponse(RestStatus.OK, """
55-
<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Prefix></Prefix></ListBucketResult>"""),
56-
handleRequest(handler, "GET", "/bucket/?prefix=")
57-
);
78+
assertListObjectsResponse(handler, "", null, """
79+
<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Prefix></Prefix><IsTruncated>false</IsTruncated>\
80+
</ListBucketResult>""");
5881

5982
final var body = randomAlphaOfLength(50);
6083
assertEquals(RestStatus.OK, handleRequest(handler, "PUT", "/bucket/path/blob", body).status());
6184
assertEquals(new TestHttpResponse(RestStatus.OK, body), handleRequest(handler, "GET", "/bucket/path/blob"));
6285

63-
assertEquals(new TestHttpResponse(RestStatus.OK, """
64-
<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Prefix></Prefix>\
86+
assertListObjectsResponse(handler, "", null, """
87+
<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Prefix></Prefix><IsTruncated>false</IsTruncated>\
6588
<Contents><Key>path/blob</Key><Size>50</Size></Contents>\
66-
</ListBucketResult>"""), handleRequest(handler, "GET", "/bucket/?prefix="));
89+
</ListBucketResult>""");
6790

68-
assertEquals(new TestHttpResponse(RestStatus.OK, """
69-
<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Prefix>path/</Prefix>\
91+
assertListObjectsResponse(handler, "path/", null, """
92+
<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Prefix>path/</Prefix><IsTruncated>false</IsTruncated>\
7093
<Contents><Key>path/blob</Key><Size>50</Size></Contents>\
71-
</ListBucketResult>"""), handleRequest(handler, "GET", "/bucket/?prefix=path/"));
94+
</ListBucketResult>""");
7295

73-
assertEquals(
74-
new TestHttpResponse(RestStatus.OK, """
75-
<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Prefix>path/other</Prefix></ListBucketResult>"""),
76-
handleRequest(handler, "GET", "/bucket/?prefix=path/other")
77-
);
96+
assertListObjectsResponse(handler, "path/other", null, """
97+
<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Prefix>path/other</Prefix><IsTruncated>false</IsTruncated>\
98+
</ListBucketResult>""");
7899

79100
assertEquals(RestStatus.OK, handleRequest(handler, "PUT", "/bucket/path/subpath1/blob", randomAlphaOfLength(50)).status());
80101
assertEquals(RestStatus.OK, handleRequest(handler, "PUT", "/bucket/path/subpath2/blob", randomAlphaOfLength(50)).status());
81-
assertEquals(new TestHttpResponse(RestStatus.OK, """
82-
<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Prefix>path/</Prefix>\
83-
<Delimiter>/</Delimiter><Contents><Key>path/blob</Key><Size>50</Size></Contents>\
102+
assertListObjectsResponse(handler, "path/", "/", """
103+
<?xml version="1.0" encoding="UTF-8"?><ListBucketResult>\
104+
<Prefix>path/</Prefix><Delimiter>/</Delimiter><IsTruncated>false</IsTruncated>\
105+
<Contents><Key>path/blob</Key><Size>50</Size></Contents>\
84106
<CommonPrefixes><Prefix>path/subpath1/</Prefix></CommonPrefixes>\
85107
<CommonPrefixes><Prefix>path/subpath2/</Prefix></CommonPrefixes>\
86-
</ListBucketResult>"""), handleRequest(handler, "GET", "/bucket/?delimiter=/&prefix=path/"));
108+
</ListBucketResult>""");
87109

88110
assertEquals(RestStatus.OK, handleRequest(handler, "DELETE", "/bucket/path/blob").status());
89111
assertEquals(RestStatus.NO_CONTENT, handleRequest(handler, "DELETE", "/bucket/path/blob").status());
90112

91-
assertEquals(new TestHttpResponse(RestStatus.OK, """
92-
<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Prefix></Prefix>\
113+
assertListObjectsResponse(handler, "", null, """
114+
<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Prefix></Prefix><IsTruncated>false</IsTruncated>\
93115
<Contents><Key>path/subpath1/blob</Key><Size>50</Size></Contents>\
94116
<Contents><Key>path/subpath2/blob</Key><Size>50</Size></Contents>\
95-
</ListBucketResult>"""), handleRequest(handler, "GET", "/bucket/?prefix="));
117+
</ListBucketResult>""");
96118

97119
assertEquals(RestStatus.OK, handleRequest(handler, "DELETE", "/bucket/path/subpath1/blob").status());
98120
assertEquals(RestStatus.OK, handleRequest(handler, "DELETE", "/bucket/path/subpath2/blob").status());
99121

100-
assertEquals(
101-
new TestHttpResponse(RestStatus.OK, """
102-
<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Prefix></Prefix></ListBucketResult>"""),
103-
handleRequest(handler, "GET", "/bucket/?prefix=")
104-
);
122+
assertListObjectsResponse(handler, "", null, """
123+
<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Prefix></Prefix><IsTruncated>false</IsTruncated>\
124+
</ListBucketResult>""");
105125
}
106126

107127
public void testGetWithBytesRange() {
@@ -216,10 +236,10 @@ public void testSingleMultipartUpload() {
216236
</CompleteMultipartUpload>""", part1Etag, part2Etag))
217237
);
218238

219-
assertEquals(new TestHttpResponse(RestStatus.OK, """
220-
<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Prefix></Prefix>\
239+
assertListObjectsResponse(handler, "", null, """
240+
<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Prefix></Prefix><IsTruncated>false</IsTruncated>\
221241
<Contents><Key>path/blob</Key><Size>100</Size></Contents>\
222-
</ListBucketResult>"""), handleRequest(handler, "GET", "/bucket/?prefix="));
242+
</ListBucketResult>""");
223243

224244
assertEquals(new TestHttpResponse(RestStatus.OK, part1 + part2), handleRequest(handler, "GET", "/bucket/path/blob"));
225245

0 commit comments

Comments
 (0)