Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 0 additions & 1 deletion sdk/parents/azure-client-sdk-parent/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -602,7 +602,6 @@

<!-- Used by many libraries to bring in annotations used by Reactor -->
<include>com.google.code.findbugs:jsr305:[3.0.2]</include> <!-- {x-include-update;com.google.code.findbugs:jsr305;external_dependency} -->

</includes>
</bannedDependencies>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,6 @@ public PageBlobClient getCustomerProvidedKeyClient(CustomerProvidedKey customerP
* @return A {@link BlobOutputStream} object used to write data to the blob.
* @throws BlobStorageException If a storage service error occurred.
*/
@ServiceMethod(returns = ReturnType.SINGLE)
public BlobOutputStream getBlobOutputStream(PageRange pageRange) {
return getBlobOutputStream(pageRange, null);
}
Expand All @@ -206,7 +205,6 @@ public BlobOutputStream getBlobOutputStream(PageRange pageRange) {
* @return A {@link BlobOutputStream} object used to write data to the blob.
* @throws BlobStorageException If a storage service error occurred.
*/
@ServiceMethod(returns = ReturnType.SINGLE)
public BlobOutputStream getBlobOutputStream(PageRange pageRange, BlobRequestConditions requestConditions) {
return BlobOutputStream.pageBlobOutputStream(pageBlobAsyncClient, pageRange, requestConditions);
}
Expand All @@ -218,7 +216,6 @@ public BlobOutputStream getBlobOutputStream(PageRange pageRange, BlobRequestCond
* @return A {@link BlobOutputStream} object used to write data to the blob.
* @throws BlobStorageException If a storage service error occurred.
*/
@ServiceMethod(returns = ReturnType.SINGLE)
public BlobOutputStream getBlobOutputStream(PageBlobOutputStreamOptions options) {
if (options == null) {
throw LOGGER.logExceptionAsError(new NullPointerException("'options' cannot be null."));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;

public class BlobApiTests extends BlobTestBase {
private BlobClient bc;
Expand Down Expand Up @@ -2153,12 +2154,24 @@ public void copyDestACFail(OffsetDateTime modified, OffsetDateTime unmodified, S
() -> bu2.copyFromUrlWithResponse(bc.getBlobUrl(), null, null, null, bac, null, null));
}

// Abort-copy tests race the service: the cross-account copy must still be pending when the abort request
// lands. A larger source widens that window (A1); combined with an assumeTrue guard on the poll status (B),
// the tests assert abort behavior when a copy is genuinely pending and skip (rather than fail) if the
// service finished the copy first. Server-to-server copy, so the larger size costs only the initial upload.
//
// The source is capped just under the test proxy's 30,000,000-byte request-body limit: a body at/above that
// limit cannot be ingested by the proxy in record or playback (it fails with HTTP 500 "Request body too large"
// or a premature connection close, and also destabilizes the shared proxy connection pool for concurrent
// tests). 28 MiB (29,360,128 bytes) stays under the cap while keeping the window wide enough to exercise abort.
private static final int ABORT_COPY_SOURCE_SIZE_BYTES = 28 * Constants.MB;

@Test
public void abortCopyBaseSimple() {
// Data has to be large enough and copied between accounts to give us enough time to abort
new SpecializedBlobClientBuilder().blobClient(bc)
.buildBlockBlobClient()
.upload(new ByteArrayInputStream(getRandomByteArray(8 * 1024 * 1024)), 8 * 1024 * 1024, true);
.upload(new ByteArrayInputStream(getRandomByteArray(ABORT_COPY_SOURCE_SIZE_BYTES)),
ABORT_COPY_SOURCE_SIZE_BYTES, true);

BlobContainerClient cu2 = alternateBlobServiceClient.getBlobContainerClient(generateBlobName());
cu2.create();
Expand All @@ -2171,6 +2184,8 @@ public void abortCopyBaseSimple() {
PollResponse<BlobCopyInfo> lastResponse = poller.poll();
assertNotNull(lastResponse);
assertNotNull(lastResponse.getValue());
assumeTrue(lastResponse.getStatus() == LongRunningOperationStatus.IN_PROGRESS,
"Copy already completed before abort could be issued; nothing pending to abort.");
Comment thread
ibrandes marked this conversation as resolved.
bu2.abortCopyFromUrl(lastResponse.getValue().getCopyId());
BlobStorageException e
= assertThrows(BlobStorageException.class, () -> bu2.abortCopyFromUrl(lastResponse.getValue().getCopyId()));
Expand All @@ -2186,7 +2201,8 @@ public void abortCopyLeaseFail() {
// Data has to be large enough and copied between accounts to give us enough time to abort
new SpecializedBlobClientBuilder().blobClient(bc)
.buildBlockBlobClient()
.upload(new ByteArrayInputStream(getRandomByteArray(8 * 1024 * 1024)), 8 * 1024 * 1024, true);
.upload(new ByteArrayInputStream(getRandomByteArray(ABORT_COPY_SOURCE_SIZE_BYTES)),
ABORT_COPY_SOURCE_SIZE_BYTES, true);

BlobContainerClient cu2 = alternateBlobServiceClient.getBlobContainerClient(generateBlobName());
cu2.create();
Expand All @@ -2202,6 +2218,8 @@ public void abortCopyLeaseFail() {
bu2.beginCopy(bc.getBlobUrl() + "?" + sas, null, null, null, null, blobRequestConditions, null));
PollResponse<BlobCopyInfo> response = poller.poll();
assertNotEquals(LongRunningOperationStatus.FAILED, response.getStatus());
assumeTrue(response.getStatus() == LongRunningOperationStatus.IN_PROGRESS,
"Copy already completed before abort could be issued; cannot observe the 412 lease mismatch.");
BlobCopyInfo blobCopyInfo = response.getValue();

BlobStorageException e = assertThrows(BlobStorageException.class,
Expand All @@ -2217,7 +2235,8 @@ public void abortCopy() {
// Data has to be large enough and copied between accounts to give us enough time to abort
new SpecializedBlobClientBuilder().blobClient(bc)
.buildBlockBlobClient()
.upload(new ByteArrayInputStream(getRandomByteArray(8 * 1024 * 1024)), 8 * 1024 * 1024, true);
.upload(new ByteArrayInputStream(getRandomByteArray(ABORT_COPY_SOURCE_SIZE_BYTES)),
ABORT_COPY_SOURCE_SIZE_BYTES, true);

BlobContainerClient cu2 = alternateBlobServiceClient.getBlobContainerClient(generateBlobName());
cu2.create();
Expand All @@ -2230,6 +2249,8 @@ public void abortCopy() {
PollResponse<BlobCopyInfo> lastResponse = poller.poll();
assertNotNull(lastResponse);
assertNotNull(lastResponse.getValue());
assumeTrue(lastResponse.getStatus() == LongRunningOperationStatus.IN_PROGRESS,
"Copy already completed before abort could be issued; nothing pending to abort.");
Response<Void> response
= bu2.abortCopyFromUrlWithResponse(lastResponse.getValue().getCopyId(), null, null, null);
HttpHeaders headers = response.getHeaders();
Expand All @@ -2247,7 +2268,8 @@ public void abortCopyLease() {
// Data has to be large enough and copied between accounts to give us enough time to abort
new SpecializedBlobClientBuilder().blobClient(bc)
.buildBlockBlobClient()
.upload(new ByteArrayInputStream(getRandomByteArray(8 * 1024 * 1024)), 8 * 1024 * 1024, true);
.upload(new ByteArrayInputStream(getRandomByteArray(ABORT_COPY_SOURCE_SIZE_BYTES)),
ABORT_COPY_SOURCE_SIZE_BYTES, true);

BlobContainerClient cu2 = alternateBlobServiceClient.getBlobContainerClient(generateContainerName());
cu2.create();
Expand All @@ -2264,6 +2286,8 @@ public void abortCopyLease() {

assertNotNull(lastResponse);
assertNotNull(lastResponse.getValue());
assumeTrue(lastResponse.getStatus() == LongRunningOperationStatus.IN_PROGRESS,
"Copy already completed before abort could be issued; nothing pending to abort.");
String copyId = lastResponse.getValue().getCopyId();
assertResponseStatusCode(bu2.abortCopyFromUrlWithResponse(copyId, leaseId, null, null), 204);
// cleanup:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -624,47 +624,54 @@ private void assertParallelDownloadFuzzyRoundTripAsync(String caseKind, int payl

if (payloadBytes >= FUZZY_PARALLEL_DOWNLOAD_FILE_ROUND_TRIP_THRESHOLD_BYTES) {
File sourceFile = getRandomFile(payloadBytes);
sourceFile.deleteOnExit();
File outFile = Files.createTempFile("blob-cv-fuzzy-parallel-dl-async", ".bin").toFile();
outFile.deleteOnExit();
Files.deleteIfExists(outFile.toPath());

BlobUploadFromFileOptions uploadOptions
= new BlobUploadFromFileOptions(sourceFile.getAbsolutePath()).setParallelTransferOptions(
new com.azure.storage.blob.models.ParallelTransferOptions().setBlockSizeLong(blockSizeBytes)
.setMaxConcurrency(maxConcurrency));

BlobDownloadToFileOptions downloadOptions
= new BlobDownloadToFileOptions(outFile.toPath().toString()).setParallelTransferOptions(parallelOptions)
.setContentValidationAlgorithm(algorithm);

StepVerifier
.create(client.uploadFromFileWithResponse(uploadOptions)
.then(client.downloadToFileWithResponse(downloadOptions)))
.assertNext(r -> assertNotNull(r.getValue(), assertionMessage))
.verifyComplete();

assertTrue(compareFiles(sourceFile, outFile, 0, payloadBytes), assertionMessage);
} else {
byte[] randomData = getRandomByteArray(payloadBytes);

if (payloadBytes > blockSizeBytes) {
File outFile = Files.createTempFile("blob-cv-fuzzy-parallel-dl-async-mp", ".bin").toFile();
outFile.deleteOnExit();
Files.deleteIfExists(outFile.toPath());
try {
BlobUploadFromFileOptions uploadOptions
= new BlobUploadFromFileOptions(sourceFile.getAbsolutePath()).setParallelTransferOptions(
new com.azure.storage.blob.models.ParallelTransferOptions().setBlockSizeLong(blockSizeBytes)
.setMaxConcurrency(maxConcurrency));

BlobDownloadToFileOptions downloadOptions = new BlobDownloadToFileOptions(outFile.toPath().toString())
.setParallelTransferOptions(parallelOptions)
.setContentValidationAlgorithm(algorithm);

StepVerifier
.create(client.upload(BinaryData.fromBytes(randomData), true)
.create(client.uploadFromFileWithResponse(uploadOptions)
.then(client.downloadToFileWithResponse(downloadOptions)))
.assertNext(r -> assertNotNull(r.getValue(), assertionMessage))
.verifyComplete();

byte[] downloaded = Files.readAllBytes(outFile.toPath());
assertArrayEquals(randomData, downloaded, assertionMessage);
assertTrue(compareFiles(sourceFile, outFile, 0, payloadBytes), assertionMessage);
} finally {
// Delete eagerly (not deleteOnExit) so large fuzzy payloads don't accumulate for the whole JVM and
// exhaust agent disk space when many cases run in the same live-test process.
deleteFileQuietly(sourceFile);
deleteFileQuietly(outFile);
}
} else {
byte[] randomData = getRandomByteArray(payloadBytes);

if (payloadBytes > blockSizeBytes) {
File outFile = Files.createTempFile("blob-cv-fuzzy-parallel-dl-async-mp", ".bin").toFile();
Files.deleteIfExists(outFile.toPath());
try {
BlobDownloadToFileOptions downloadOptions
= new BlobDownloadToFileOptions(outFile.toPath().toString())
.setParallelTransferOptions(parallelOptions)
.setContentValidationAlgorithm(algorithm);

StepVerifier
.create(client.upload(BinaryData.fromBytes(randomData), true)
.then(client.downloadToFileWithResponse(downloadOptions)))
.assertNext(r -> assertNotNull(r.getValue(), assertionMessage))
.verifyComplete();

byte[] downloaded = Files.readAllBytes(outFile.toPath());
assertArrayEquals(randomData, downloaded, assertionMessage);
} finally {
deleteFileQuietly(outFile);
}
} else {
BlobDownloadContentOptions downloadOptions
= new BlobDownloadContentOptions().setContentValidationAlgorithm(algorithm);
Expand All @@ -687,4 +694,14 @@ private void assertParallelDownloadFuzzyRoundTripAsync(String caseKind, int payl
assertTrue(hasStructuredMessageDownloadRequestHeaders(recorded, false), assertionMessage);
}

/**
* Deletes a temp file eagerly, falling back to {@link File#deleteOnExit()} only if the immediate delete fails.
* Prevents large fuzzy round-trip payloads from accumulating on disk across the many cases that share a JVM.
*/
private static void deleteFileQuietly(File file) {
if (file != null && file.exists() && !file.delete()) {
file.deleteOnExit();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -839,15 +839,26 @@ public void appendBlockLiveRandomRoundTripDataIntegrity() throws Exception {
outFile.deleteOnExit();

try {
try (AsynchronousFileChannel channel
= AsynchronousFileChannel.open(sourceFile.toPath(), StandardOpenOption.READ)) {
FluxUtil.readFile(channel, maxAppendBlockBytes, 0, chosenPayloadSizeBytes).concatMap(bb -> {
AppendBlobAppendBlockOptions appendOptions = new AppendBlobAppendBlockOptions()
.setContentValidationAlgorithm(ContentValidationAlgorithm.CRC64);
return client.appendBlockWithResponse(Flux.just(bb), bb.remaining(), appendOptions);
}).then().block();
}
StepVerifier.create(client.create().then(blobClient.downloadToFile(outFile.getPath(), true)))
// Append blobs must be created before any block can be appended; otherwise the first
// appendBlock call fails with 404 BlobNotFound. Create, append every block, then download,
// all chained into a single reactive pipeline (no blocking calls in the test).
StepVerifier.create(client.create()
.thenMany(
Flux.using(() -> AsynchronousFileChannel.open(sourceFile.toPath(), StandardOpenOption.READ),
channel -> FluxUtil.readFile(channel, maxAppendBlockBytes, 0, chosenPayloadSizeBytes)
.concatMap(bb -> {
AppendBlobAppendBlockOptions appendOptions = new AppendBlobAppendBlockOptions()
.setContentValidationAlgorithm(ContentValidationAlgorithm.CRC64);
return client.appendBlockWithResponse(Flux.just(bb), bb.remaining(), appendOptions);
}),
channel -> {
try {
channel.close();
} catch (IOException e) {
throw new java.io.UncheckedIOException(e);
}
}))
.then(blobClient.downloadToFile(outFile.getPath(), true)))
.assertNext(Assertions::assertNotNull)
.verifyComplete();
assertTrue(compareFiles(sourceFile, outFile, 0, chosenPayloadSizeBytes), prefix);
Expand Down
Loading