Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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,19 @@ 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.
private static final int ABORT_COPY_SOURCE_SIZE_BYTES = 64 * 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 +2179,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 +2196,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 +2213,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 +2230,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 +2244,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 +2263,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 +2281,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,27 @@ 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) {
Comment thread
ibrandes marked this conversation as resolved.
throw new RuntimeException(e);
}
Comment thread
ibrandes marked this conversation as resolved.
}))
.then(blobClient.downloadToFile(outFile.getPath(), true)))
.assertNext(Assertions::assertNotNull)
.verifyComplete();
assertTrue(compareFiles(sourceFile, outFile, 0, chosenPayloadSizeBytes), prefix);
Expand Down
Loading
Loading