Skip to content

BlobContainer: add copyBlob method #125737

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 39 commits into from
Apr 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
13722c8
BlobContainer: add copyBlob method
bcully Mar 26, 2025
9c9eeb3
trigger s3 tests (revert before merge)
bcully Mar 26, 2025
e2c3d74
[CI] Auto commit changes from spotless
elasticsearchmachine Mar 26, 2025
feb6367
FsBlobContainerTests: small improvements to testCopy
bcully Mar 27, 2025
c64a3f0
debugging FsBlobContainerTests::testCopy on ci
bcully Mar 27, 2025
11da091
Merge branch 'main' of github.com:elastic/elasticsearch into ES-11059
bcully Mar 27, 2025
2b93478
is it sourceContainer vs targetContainer?
bcully Mar 27, 2025
27656b2
Use get instead of getOrDefault for metrics in repo integ test
bcully Mar 28, 2025
89ae18a
copyBlob: use "destination" instead of "target"
bcully Mar 28, 2025
7ec3301
Merge branch 'main' of github.com:elastic/elasticsearch into ES-11059
bcully Mar 28, 2025
ec271f5
Remove failIfAlreadyExists from BlobContainer.copyBlob
bcully Mar 28, 2025
918d80b
Add copyBlob to ESBlobStoreRepositoryIntegTestCase
bcully Mar 28, 2025
faa0a83
Preinitialize Azure metrics for BlobStoreRepositoryTests too
bcully Mar 29, 2025
4d424bf
Merge branch 'main' of github.com:elastic/elasticsearch into ES-11059
bcully Mar 31, 2025
7ca903e
Add support for S3 multipart copy
bcully Apr 1, 2025
8939bb7
Change copyBlob API to be explicitly copyFrom
bcully Apr 2, 2025
f4aad52
Use multipart copy if source blob size is too large for copyObject
bcully Apr 2, 2025
a2ffa47
[CI] Auto commit changes from spotless
elasticsearchmachine Apr 2, 2025
6e01aa1
spotless
bcully Apr 2, 2025
f73085a
Merge branch 'ES-11059' of github.com:bcully/elasticsearch into ES-11059
bcully Apr 2, 2025
b61c151
Update GoogleCloudStorageStatsCollectorHttpHandler constructor
bcully Apr 2, 2025
0ca124c
Merge branch 'main' of github.com:elastic/elasticsearch into ES-11059
bcully Apr 2, 2025
0fc7b37
FSBlobContainerTests: workaround for occasional testCopy failure on CI
bcully Apr 2, 2025
23ac1bd
Merge branch 'main' of github.com:elastic/elasticsearch into ES-11059
bcully Apr 7, 2025
cf0f252
BlobAnalyzeAction: rough first cut at copy
bcully Apr 8, 2025
8313592
GoogleCloudStorageBlobStoreRepositoryTests: post-merge fixup
bcully Apr 8, 2025
f3e1e21
S3 copyBlob: convert amazon exception to NoSuchFileException when sou…
bcully Apr 8, 2025
edad041
[CI] Auto commit changes from spotless
elasticsearchmachine Apr 8, 2025
ca737a8
Fix up BWC
DaveCTurner Apr 8, 2025
d9db70d
Fix failure-path test
DaveCTurner Apr 8, 2025
dd7ded8
PR feedback: pass copy blob name as request parameter.
bcully Apr 8, 2025
892e330
[CI] Auto commit changes from spotless
elasticsearchmachine Apr 8, 2025
cb2e324
Exercise multipart copy randomly in testWriteMaybeCopyRead
bcully Apr 8, 2025
f905cc9
Merge commit '728eb75' into ES-11059
bcully Apr 8, 2025
5e2b84b
Revert "trigger s3 tests (revert before merge)"
bcully Apr 8, 2025
25e3a1b
Use HttpHeaderParser.parseRangeHeader to parse copy source range.
bcully Apr 9, 2025
a9ff4b9
Make max copy size before multipart a setting.
bcully Apr 9, 2025
f02cd99
Merge commit '3bac50e' into ES-11059
bcully Apr 9, 2025
582b22c
Fix blobstore mock in S3BlobStoreContainerTests::testCopy
bcully Apr 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.NoSuchFileException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collection;
import java.util.Collections;
Expand Down Expand Up @@ -241,7 +242,7 @@ protected String requestUniqueId(final HttpExchange exchange) {
private static class AzureHTTPStatsCollectorHandler extends HttpStatsCollectorHandler {

private AzureHTTPStatsCollectorHandler(HttpHandler delegate) {
super(delegate);
super(delegate, Arrays.stream(AzureBlobStore.Operation.values()).map(AzureBlobStore.Operation::getKey).toArray(String[]::new));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ protected boolean canFailRequest(final HttpExchange exchange) {
private static class GoogleCloudStorageStatsCollectorHttpHandler extends HttpStatsCollectorHandler {

GoogleCloudStorageStatsCollectorHttpHandler(final HttpHandler delegate) {
super(delegate);
super(delegate, Arrays.stream(StorageOperation.values()).map(StorageOperation::key).toArray(String[]::new));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,4 +228,56 @@ public void testReadFromPositionLargerThanBlobLength() {
e -> asInstanceOf(AmazonS3Exception.class, e.getCause()).getStatusCode() == RestStatus.REQUESTED_RANGE_NOT_SATISFIED.getStatus()
);
}

public void testCopy() {
final var sourceBlobName = randomIdentifier();
final var blobBytes = randomBytesReference(randomIntBetween(100, 2_000));
final var destinationBlobName = randomIdentifier();

final var repository = getRepository();

final var targetBytes = executeOnBlobStore(repository, sourceBlobContainer -> {
sourceBlobContainer.writeBlob(randomPurpose(), sourceBlobName, blobBytes, true);

final var destinationBlobContainer = repository.blobStore().blobContainer(repository.basePath().add("target"));
destinationBlobContainer.copyBlob(
randomPurpose(),
sourceBlobContainer,
sourceBlobName,
destinationBlobName,
blobBytes.length()
);

return destinationBlobContainer.readBlob(randomPurpose(), destinationBlobName).readAllBytes();
});

assertArrayEquals(BytesReference.toBytes(blobBytes), targetBytes);
}

public void testMultipartCopy() {
final var sourceBlobName = randomIdentifier();
// executeMultipart requires a minimum part size of 5 MiB
final var blobBytes = randomBytesReference(randomIntBetween(5 * 1024 * 1024, 10 * 1024 * 1024));
final var destinationBlobName = randomIdentifier();

final var repository = getRepository();

final var targetBytes = executeOnBlobStore(repository, sourceBlobContainer -> {
sourceBlobContainer.writeBlob(randomPurpose(), sourceBlobName, blobBytes, true);

final S3BlobContainer destinationBlobContainer = (S3BlobContainer) repository.blobStore()
.blobContainer(repository.basePath().add("target"));
destinationBlobContainer.executeMultipartCopy(
randomPurpose(),
(S3BlobContainer) sourceBlobContainer,
sourceBlobName,
destinationBlobName,
blobBytes.length()
);

return destinationBlobContainer.readBlob(randomPurpose(), destinationBlobName).readAllBytes();
});

assertArrayEquals(BytesReference.toBytes(blobBytes), targetBytes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
Expand Down Expand Up @@ -621,6 +622,12 @@ long getLargeBlobThresholdInBytes() {
return ByteSizeUnit.MB.toBytes(1L);
}

@Override
long getMaxCopySizeBeforeMultipart() {
// on my laptop 10K exercises this better but larger values should be fine for nightlies
return ByteSizeUnit.MB.toBytes(1L);
}

@Override
void ensureMultiPartUploadSize(long blobSize) {}
};
Expand Down Expand Up @@ -688,7 +695,7 @@ protected class S3StatsCollectorHttpHandler extends HttpStatsCollectorHandler {
private final Map<S3BlobStore.StatsKey, AtomicLong> metricsCount = ConcurrentCollections.newConcurrentMap();

S3StatsCollectorHttpHandler(final HttpHandler delegate) {
super(delegate);
super(delegate, Arrays.stream(S3BlobStore.Operation.values()).map(S3BlobStore.Operation::getKey).toArray(String[]::new));
}

private S3HttpHandler.S3Request parseRequest(HttpExchange exchange) {
Expand Down Expand Up @@ -736,9 +743,17 @@ public void maybeTrack(HttpExchange exchange) {
k -> new AtomicLong()
).incrementAndGet();
} else if (request.isPutObjectRequest()) {
trackRequest("PutObject");
metricsCount.computeIfAbsent(new S3BlobStore.StatsKey(S3BlobStore.Operation.PUT_OBJECT, purpose), k -> new AtomicLong())
.incrementAndGet();
if (exchange.getRequestHeaders().containsKey(S3BlobStore.CUSTOM_QUERY_PARAMETER_COPY_SOURCE)) {
trackRequest("CopyObject");
metricsCount.computeIfAbsent(
new S3BlobStore.StatsKey(S3BlobStore.Operation.COPY_OBJECT, purpose),
k -> new AtomicLong()
).incrementAndGet();
} else {
trackRequest("PutObject");
metricsCount.computeIfAbsent(new S3BlobStore.StatsKey(S3BlobStore.Operation.PUT_OBJECT, purpose), k -> new AtomicLong())
.incrementAndGet();
}
} else if (request.isMultiObjectDeleteRequest()) {
trackRequest("DeleteObjects");
metricsCount.computeIfAbsent(new S3BlobStore.StatsKey(S3BlobStore.Operation.DELETE_OBJECTS, purpose), k -> new AtomicLong())
Expand Down
Loading