From 54ca2892f773b4d2adfda8d430c55e5d75963dd1 Mon Sep 17 00:00:00 2001 From: Gunjan Singh Date: Wed, 29 Apr 2026 19:23:27 +0530 Subject: [PATCH 01/11] adding stress tests for content validation decoder --- ...ContentValidationDecoderStressOptions.java | 26 +++++ .../ContentValidationDownloadContent.java | 61 ++++++++++ .../ContentValidationDownloadStream.java | 66 +++++++++++ .../ContentValidationDownloadToFile.java | 104 ++++++++++++++++++ .../ContentValidationOpenInputStream.java | 69 ++++++++++++ ...ValidationOpenSeekableByteChannelRead.java | 72 ++++++++++++ 6 files changed, 398 insertions(+) create mode 100644 sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/ContentValidationDecoderStressOptions.java create mode 100644 sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/ContentValidationDownloadContent.java create mode 100644 sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/ContentValidationDownloadStream.java create mode 100644 sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/ContentValidationDownloadToFile.java create mode 100644 sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/ContentValidationOpenInputStream.java create mode 100644 sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/ContentValidationOpenSeekableByteChannelRead.java diff --git a/sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/ContentValidationDecoderStressOptions.java b/sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/ContentValidationDecoderStressOptions.java new file mode 100644 index 000000000000..bc16742ac621 --- /dev/null +++ b/sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/ContentValidationDecoderStressOptions.java @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.storage.blob.stress; + +import com.azure.storage.common.ContentValidationAlgorithm; +import com.azure.storage.stress.StorageStressOptions; +import com.beust.jcommander.Parameter; + +/** + * Options for stress scenarios that enable transactional response content validation on downloads + * (CRC64 / structured message). See {@link com.azure.storage.blob.BlobContentValidationDownloadTests}. + */ +public class ContentValidationDecoderStressOptions extends StorageStressOptions { + /** + * Response content validation behavior for download APIs. Use CRC64 or AUTO to exercise content validation. + * NONE disables response validation. + */ + @Parameter(names = { "--contentValidationAlgorithm" }, + description = "CRC64 (default), AUTO, or NONE") + private ContentValidationAlgorithm contentValidationAlgorithm = ContentValidationAlgorithm.CRC64; + + public ContentValidationAlgorithm getContentValidationAlgorithm() { + return contentValidationAlgorithm; + } +} diff --git a/sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/ContentValidationDownloadContent.java b/sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/ContentValidationDownloadContent.java new file mode 100644 index 000000000000..058a080694ec --- /dev/null +++ b/sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/ContentValidationDownloadContent.java @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.storage.blob.stress; + +import com.azure.core.util.Context; +import com.azure.storage.blob.BlobAsyncClient; +import com.azure.storage.blob.BlobClient; +import com.azure.storage.blob.options.BlobDownloadContentOptions; +import com.azure.storage.blob.stress.utils.OriginalContent; +import reactor.core.publisher.Mono; + +/** + * Download content with + * {@link BlobDownloadContentOptions#setContentValidationAlgorithm} enabled. + * Verifies the correctness of the download response content via CRC. + */ +public class ContentValidationDownloadContent extends BlobScenarioBase { + private final OriginalContent originalContent = new OriginalContent(); + private final BlobClient syncClient; + private final BlobAsyncClient asyncClient; + private final BlobAsyncClient asyncNoFaultClient; + + public ContentValidationDownloadContent(ContentValidationDecoderStressOptions options) { + super(options); + String blobName = generateBlobName(); + this.asyncNoFaultClient = getAsyncContainerClientNoFault().getBlobAsyncClient(blobName); + this.syncClient = getSyncContainerClient().getBlobClient(blobName); + this.asyncClient = getAsyncContainerClient().getBlobAsyncClient(blobName); + } + + @Override + protected void runInternal(Context span) { + originalContent.checkMatch( + syncClient.downloadContentWithResponse( + new BlobDownloadContentOptions() + .setContentValidationAlgorithm(options.getContentValidationAlgorithm()), + null, span).getValue(), + span).block(); + } + + @Override + protected Mono runInternalAsync(Context span) { + return asyncClient.downloadContentWithResponse( + new BlobDownloadContentOptions() + .setContentValidationAlgorithm(options.getContentValidationAlgorithm())) + .flatMap(r -> originalContent.checkMatch(r.getValue(), span)); + } + + @Override + public Mono setupAsync() { + return super.setupAsync() + .then(originalContent.setupBlob(asyncNoFaultClient, options.getSize())); + } + + @Override + public Mono cleanupAsync() { + return asyncNoFaultClient.deleteIfExists() + .then(super.cleanupAsync()); + } +} diff --git a/sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/ContentValidationDownloadStream.java b/sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/ContentValidationDownloadStream.java new file mode 100644 index 000000000000..1fcaf549657a --- /dev/null +++ b/sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/ContentValidationDownloadStream.java @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.storage.blob.stress; + +import com.azure.core.util.Context; +import com.azure.storage.blob.BlobAsyncClient; +import com.azure.storage.blob.BlobClient; +import com.azure.storage.blob.options.BlobDownloadStreamOptions; +import com.azure.storage.blob.stress.utils.OriginalContent; +import com.azure.storage.stress.CrcOutputStream; +import reactor.core.publisher.Mono; + +import java.io.IOException; + +/** + * Streaming blob download with + * {@link BlobDownloadStreamOptions#setContentValidationAlgorithm} enabled. + * Verifies the correctness of the download response content via CRC. + */ +public class ContentValidationDownloadStream extends BlobScenarioBase { + private final OriginalContent originalContent = new OriginalContent(); + private final BlobClient syncClient; + private final BlobAsyncClient asyncClient; + private final BlobAsyncClient asyncNoFaultClient; + + public ContentValidationDownloadStream(ContentValidationDecoderStressOptions options) { + super(options); + String blobName = generateBlobName(); + this.asyncNoFaultClient = getAsyncContainerClientNoFault().getBlobAsyncClient(blobName); + this.syncClient = getSyncContainerClient().getBlobClient(blobName); + this.asyncClient = getAsyncContainerClient().getBlobAsyncClient(blobName); + } + + @Override + protected void runInternal(Context span) throws IOException { + try (CrcOutputStream outputStream = new CrcOutputStream()) { + syncClient.downloadStreamWithResponse(outputStream, + new BlobDownloadStreamOptions() + .setContentValidationAlgorithm(options.getContentValidationAlgorithm()), + null, span); + outputStream.close(); + originalContent.checkMatch(outputStream.getContentInfo(), span).block(); + } + } + + @Override + protected Mono runInternalAsync(Context span) { + return asyncClient.downloadStreamWithResponse( + new BlobDownloadStreamOptions() + .setContentValidationAlgorithm(options.getContentValidationAlgorithm())) + .flatMap(response -> originalContent.checkMatch(response.getValue(), span)); + } + + @Override + public Mono setupAsync() { + return super.setupAsync() + .then(originalContent.setupBlob(asyncNoFaultClient, options.getSize())); + } + + @Override + public Mono cleanupAsync() { + return asyncNoFaultClient.deleteIfExists() + .then(super.cleanupAsync()); + } +} diff --git a/sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/ContentValidationDownloadToFile.java b/sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/ContentValidationDownloadToFile.java new file mode 100644 index 000000000000..da52199b1339 --- /dev/null +++ b/sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/ContentValidationDownloadToFile.java @@ -0,0 +1,104 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.storage.blob.stress; + +import com.azure.core.util.BinaryData; +import com.azure.core.util.Context; +import com.azure.core.util.logging.ClientLogger; +import com.azure.storage.blob.BlobAsyncClient; +import com.azure.storage.blob.BlobClient; +import com.azure.storage.blob.options.BlobDownloadToFileOptions; +import com.azure.storage.blob.stress.utils.OriginalContent; +import com.azure.storage.common.ParallelTransferOptions; +import reactor.core.publisher.Mono; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Duration; +import java.util.UUID; + +/** + * Download to file with + * {@link BlobDownloadToFileOptions#setContentValidationAlgorithm} enabled. + * Verifies the correctness of the download response content via CRC. + */ +public class ContentValidationDownloadToFile extends BlobScenarioBase { + private static final ClientLogger LOGGER = new ClientLogger(ContentValidationDownloadToFile.class); + private final Path directoryPath; + private final OriginalContent originalContent = new OriginalContent(); + private final BlobClient syncClient; + private final BlobAsyncClient asyncClient; + private final BlobAsyncClient asyncNoFaultClient; + private final ParallelTransferOptions parallelTransferOptions; + + public ContentValidationDownloadToFile(ContentValidationDecoderStressOptions options) { + super(options); + this.directoryPath = getTempPath("test"); + String blobName = generateBlobName(); + this.asyncNoFaultClient = getAsyncContainerClientNoFault().getBlobAsyncClient(blobName); + this.syncClient = getSyncContainerClient().getBlobClient(blobName); + this.asyncClient = getAsyncContainerClient().getBlobAsyncClient(blobName); + this.parallelTransferOptions = new ParallelTransferOptions() + .setMaxConcurrency(options.getMaxConcurrency()); + } + + @Override + protected void runInternal(Context span) { + Path downloadPath = directoryPath.resolve(UUID.randomUUID() + ".txt"); + BlobDownloadToFileOptions blobOptions = new BlobDownloadToFileOptions(downloadPath.toString()) + .setParallelTransferOptions(parallelTransferOptions) + .setContentValidationAlgorithm(options.getContentValidationAlgorithm()); + + try { + syncClient.downloadToFileWithResponse(blobOptions, Duration.ofSeconds(options.getDuration()), span); + originalContent.checkMatch(BinaryData.fromFile(downloadPath), span).block(); + } finally { + deleteFile(downloadPath); + } + } + + @Override + protected Mono runInternalAsync(Context span) { + return Mono.using( + () -> directoryPath.resolve(UUID.randomUUID() + ".txt"), + path -> asyncClient.downloadToFileWithResponse( + new BlobDownloadToFileOptions(path.toString()) + .setParallelTransferOptions(parallelTransferOptions) + .setContentValidationAlgorithm(options.getContentValidationAlgorithm())) + .flatMap(ignored -> originalContent.checkMatch(BinaryData.fromFile(path), span)), + ContentValidationDownloadToFile::deleteFile); + } + + @Override + public Mono setupAsync() { + return super.setupAsync() + .then(originalContent.setupBlob(asyncNoFaultClient, options.getSize())); + } + + @Override + public Mono cleanupAsync() { + return asyncNoFaultClient.deleteIfExists() + .then(super.cleanupAsync()); + } + + private Path getTempPath(String prefix) { + try { + return Files.createTempDirectory(prefix); + } catch (IOException e) { + throw LOGGER.logExceptionAsError(new UncheckedIOException(e)); + } + } + + private static void deleteFile(Path path) { + try { + Files.deleteIfExists(path); + } catch (Throwable e) { + LOGGER.atError() + .addKeyValue("path", path) + .log("failed to delete file", e); + } + } +} diff --git a/sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/ContentValidationOpenInputStream.java b/sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/ContentValidationOpenInputStream.java new file mode 100644 index 000000000000..5d282c7e9c7a --- /dev/null +++ b/sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/ContentValidationOpenInputStream.java @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.storage.blob.stress; + +import com.azure.core.util.Context; +import com.azure.core.util.logging.ClientLogger; +import com.azure.storage.blob.BlobAsyncClient; +import com.azure.storage.blob.BlobClient; +import com.azure.storage.blob.options.BlobInputStreamOptions; +import com.azure.storage.blob.stress.utils.OriginalContent; +import com.azure.storage.stress.CrcInputStream; +import reactor.core.publisher.Mono; + +import java.io.IOException; +import java.io.InputStream; + +import static com.azure.core.util.FluxUtil.monoError; + +/** + * Open input stream with {@link BlobInputStreamOptions#setContentValidationAlgorithm} enabled (sync only). + * Verifies the correctness of the download response content via CRC. + */ +public class ContentValidationOpenInputStream extends BlobScenarioBase { + private static final ClientLogger LOGGER = new ClientLogger(ContentValidationOpenInputStream.class); + private final OriginalContent originalContent = new OriginalContent(); + private final BlobClient syncClient; + private final BlobAsyncClient asyncNoFaultClient; + + public ContentValidationOpenInputStream(ContentValidationDecoderStressOptions options) { + super(options); + String blobName = generateBlobName(); + this.syncClient = getSyncContainerClient().getBlobClient(blobName); + this.asyncNoFaultClient = getAsyncContainerClientNoFault().getBlobAsyncClient(blobName); + } + + @Override + protected void runInternal(Context span) throws IOException { + try (InputStream stream = syncClient.openInputStream( + new BlobInputStreamOptions() + .setContentValidationAlgorithm(options.getContentValidationAlgorithm()), + span)) { + try (CrcInputStream crcStream = new CrcInputStream(stream)) { + byte[] buffer = new byte[8192]; + while (crcStream.read(buffer) != -1) { + // do nothing + } + originalContent.checkMatch(crcStream.getContentInfo(), span).block(); + } + } + } + + @Override + protected Mono runInternalAsync(Context span) { + return monoError(LOGGER, new RuntimeException("openInputStream() does not exist on the async client")); + } + + @Override + public Mono setupAsync() { + return super.setupAsync() + .then(originalContent.setupBlob(asyncNoFaultClient, options.getSize())); + } + + @Override + public Mono cleanupAsync() { + return asyncNoFaultClient.deleteIfExists() + .then(super.cleanupAsync()); + } +} diff --git a/sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/ContentValidationOpenSeekableByteChannelRead.java b/sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/ContentValidationOpenSeekableByteChannelRead.java new file mode 100644 index 000000000000..8de4aefd8832 --- /dev/null +++ b/sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/ContentValidationOpenSeekableByteChannelRead.java @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.storage.blob.stress; + +import com.azure.core.util.Context; +import com.azure.core.util.logging.ClientLogger; +import com.azure.storage.blob.BlobAsyncClient; +import com.azure.storage.blob.BlobClient; +import com.azure.storage.blob.models.BlobSeekableByteChannelReadResult; +import com.azure.storage.blob.options.BlobSeekableByteChannelReadOptions; +import com.azure.storage.blob.stress.utils.OriginalContent; +import com.azure.storage.stress.CrcInputStream; +import reactor.core.publisher.Mono; + +import java.io.IOException; +import java.nio.channels.Channels; + +import static com.azure.core.util.FluxUtil.monoError; + +/** + * Seekable byte channel read with {@link BlobSeekableByteChannelReadOptions#setContentValidationAlgorithm} + * enabled (sync only). + * Verifies the correctness of the download response content via CRC. + */ +public class ContentValidationOpenSeekableByteChannelRead + extends BlobScenarioBase { + private static final ClientLogger LOGGER = new ClientLogger(ContentValidationOpenSeekableByteChannelRead.class); + private final OriginalContent originalContent = new OriginalContent(); + private final BlobClient syncClient; + private final BlobAsyncClient asyncNoFaultClient; + + public ContentValidationOpenSeekableByteChannelRead(ContentValidationDecoderStressOptions options) { + super(options); + String blobName = generateBlobName(); + this.asyncNoFaultClient = getAsyncContainerClientNoFault().getBlobAsyncClient(blobName); + this.syncClient = getSyncContainerClient().getBlobClient(blobName); + } + + @Override + protected void runInternal(Context span) throws IOException { + BlobSeekableByteChannelReadResult result = syncClient.openSeekableByteChannelRead( + new BlobSeekableByteChannelReadOptions() + .setContentValidationAlgorithm(options.getContentValidationAlgorithm()), + span); + try (CrcInputStream crcStream = new CrcInputStream(Channels.newInputStream(result.getChannel()))) { + byte[] buffer = new byte[8192]; + while (crcStream.read(buffer) != -1) { + // do nothing + } + originalContent.checkMatch(crcStream.getContentInfo(), span).block(); + } + } + + @Override + protected Mono runInternalAsync(Context span) { + return monoError(LOGGER, + new RuntimeException("openSeekableByteChannelRead() does not exist on the async client")); + } + + @Override + public Mono setupAsync() { + return super.setupAsync() + .then(originalContent.setupBlob(asyncNoFaultClient, options.getSize())); + } + + @Override + public Mono cleanupAsync() { + return asyncNoFaultClient.deleteIfExists() + .then(super.cleanupAsync()); + } +} From 73ecddd09f5fe61479acad50549754d8171249ce Mon Sep 17 00:00:00 2001 From: browndav Date: Wed, 29 Apr 2026 16:38:48 -0400 Subject: [PATCH 02/11] fix TelemetryHelper, add tru to registerOberservers() --- .../src/main/java/com/azure/storage/stress/TelemetryHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/storage/azure-storage-stress/src/main/java/com/azure/storage/stress/TelemetryHelper.java b/sdk/storage/azure-storage-stress/src/main/java/com/azure/storage/stress/TelemetryHelper.java index 4bf6e523eae1..b633479100b1 100644 --- a/sdk/storage/azure-storage-stress/src/main/java/com/azure/storage/stress/TelemetryHelper.java +++ b/sdk/storage/azure-storage-stress/src/main/java/com/azure/storage/stress/TelemetryHelper.java @@ -124,7 +124,7 @@ public String getDescription() { Cpu.registerObservers(otel); MemoryPools.registerObservers(otel); Threads.registerObservers(otel); - GarbageCollector.registerObservers(otel); + GarbageCollector.registerObservers(otel, true); OpenTelemetryAppender.install(otel); return otel; } From 774bb19a48c58960e68c06c09783aad7c9827ead Mon Sep 17 00:00:00 2001 From: browndav-msft Date: Mon, 16 Mar 2026 18:37:14 -0400 Subject: [PATCH 03/11] Storage - Fix Flaky Stress Tests (#48359) * removed enableDeterministic * change .delete() to .deleteIfExists() * remove Sinks.EmitFailureHandler.FAIL_FAST from CrcInputStream - read functions had FAIL_FAST which would throw an error when the stream had reached then end and we wanted to read from the stream again. So we removed from both reads. - refactor code so that the exit criteria is a tthe beginning - refactor the emitContentInfo for dry * prevent crashes on reattempted close on stream - changed emitValue to tryEmitValue - remove Sinks.EmitFailureHandler.FAIL_FAST so that multiple closes does not cause an error to be thrown * fix telemetry so that it doesnt swallow errors * roll back two deps because they were causing failures in the containers - opentelemetry-runtime-telemetry-java8 from 2.24.0-alpha -> 2.15.0-alpha - opentelemetry-logback-appender-1.0 from 2.24.0-alpha -> 2.15.0-alpha * rollback azure-client-sdk-parent linting extensions from 1.0.0-beta.2 t0 beta.1 * revert linting extensions to beta2 * remove comments with old code * add logging for errors * remove catches for double close issue and okay status * recursively delete files then delete the directory * change to sync deletes, refactor for easier reading * restructing share clean up so super calls only once * incorporate copilot suggestions * incorporate copilot suggestions * incorporate copilot suggestions * incorporate copilot suggestions * fix all deletes to make sync and wrap in try-catch * fix tests so that super.globalCleanupAsync() is only called once * change telemetry to loggin only returns final state instead of failed retries when ultimately successful * undo versio downgrade for linting-extensions * Fixing spacing in error messages Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * refactor datalake delete all so that it is easier to read * refactor runAsync in ShareScenarioBase so retry failures dont show as failures upon success --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- sdk/storage/azure-storage-blob-stress/pom.xml | 4 ++-- sdk/storage/azure-storage-file-datalake-stress/pom.xml | 4 ++-- sdk/storage/azure-storage-file-share-stress/pom.xml | 4 ++-- sdk/storage/azure-storage-stress/pom.xml | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/sdk/storage/azure-storage-blob-stress/pom.xml b/sdk/storage/azure-storage-blob-stress/pom.xml index a64ef5b1f1d3..ec72a4c2cf2f 100644 --- a/sdk/storage/azure-storage-blob-stress/pom.xml +++ b/sdk/storage/azure-storage-blob-stress/pom.xml @@ -57,12 +57,12 @@ io.opentelemetry.instrumentation opentelemetry-runtime-telemetry-java8 - 2.24.0-alpha + 2.15.0-alpha io.opentelemetry.instrumentation opentelemetry-logback-appender-1.0 - 2.24.0-alpha + 2.15.0-alpha diff --git a/sdk/storage/azure-storage-file-datalake-stress/pom.xml b/sdk/storage/azure-storage-file-datalake-stress/pom.xml index dc0b24e03b62..4080f9c2098d 100644 --- a/sdk/storage/azure-storage-file-datalake-stress/pom.xml +++ b/sdk/storage/azure-storage-file-datalake-stress/pom.xml @@ -57,12 +57,12 @@ io.opentelemetry.instrumentation opentelemetry-runtime-telemetry-java8 - 2.24.0-alpha + 2.15.0-alpha io.opentelemetry.instrumentation opentelemetry-logback-appender-1.0 - 2.24.0-alpha + 2.15.0-alpha diff --git a/sdk/storage/azure-storage-file-share-stress/pom.xml b/sdk/storage/azure-storage-file-share-stress/pom.xml index fff0eb6173c3..e1730f507b00 100644 --- a/sdk/storage/azure-storage-file-share-stress/pom.xml +++ b/sdk/storage/azure-storage-file-share-stress/pom.xml @@ -57,12 +57,12 @@ io.opentelemetry.instrumentation opentelemetry-runtime-telemetry-java8 - 2.24.0-alpha + 2.15.0-alpha io.opentelemetry.instrumentation opentelemetry-logback-appender-1.0 - 2.24.0-alpha + 2.15.0-alpha diff --git a/sdk/storage/azure-storage-stress/pom.xml b/sdk/storage/azure-storage-stress/pom.xml index cf1888ebf045..32028b16e7f3 100644 --- a/sdk/storage/azure-storage-stress/pom.xml +++ b/sdk/storage/azure-storage-stress/pom.xml @@ -52,12 +52,12 @@ io.opentelemetry.instrumentation opentelemetry-runtime-telemetry-java8 - 2.24.0-alpha + 2.15.0-alpha io.opentelemetry.instrumentation opentelemetry-logback-appender-1.0 - 2.24.0-alpha + 2.15.0-alpha com.azure From 1f3373d89f714f69df656092ee93d3d8fd311269 Mon Sep 17 00:00:00 2001 From: browndav Date: Thu, 30 Apr 2026 20:07:51 -0400 Subject: [PATCH 04/11] changes to cvdownload content, lazyload versus eagerloading --- .../stress/ContentValidationDownloadContent.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/ContentValidationDownloadContent.java b/sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/ContentValidationDownloadContent.java index 058a080694ec..07c549475e33 100644 --- a/sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/ContentValidationDownloadContent.java +++ b/sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/ContentValidationDownloadContent.java @@ -3,10 +3,13 @@ package com.azure.storage.blob.stress; +import com.azure.core.http.HttpHeaderName; +import com.azure.core.util.BinaryData; import com.azure.core.util.Context; import com.azure.storage.blob.BlobAsyncClient; import com.azure.storage.blob.BlobClient; import com.azure.storage.blob.options.BlobDownloadContentOptions; +import com.azure.storage.blob.options.BlobDownloadStreamOptions; import com.azure.storage.blob.stress.utils.OriginalContent; import reactor.core.publisher.Mono; @@ -41,10 +44,15 @@ protected void runInternal(Context span) { @Override protected Mono runInternalAsync(Context span) { - return asyncClient.downloadContentWithResponse( - new BlobDownloadContentOptions() + // TODO return downloadContent once it stops buffering. + return asyncClient.downloadStreamWithResponse( + new BlobDownloadStreamOptions() .setContentValidationAlgorithm(options.getContentValidationAlgorithm())) - .flatMap(r -> originalContent.checkMatch(r.getValue(), span)); + .flatMap(response -> { + long contentLength = Long.valueOf(response.getHeaders().getValue(HttpHeaderName.CONTENT_LENGTH)); + return BinaryData.fromFlux(response.getValue(), contentLength, false); + }) + .flatMap(bd -> originalContent.checkMatch(bd, span)); } @Override From 82cb92d505a7daa07224438b43da0e9f3553fe82 Mon Sep 17 00:00:00 2001 From: browndav Date: Thu, 30 Apr 2026 20:08:31 -0400 Subject: [PATCH 05/11] update to TelemetryHelper based on previous cherry picked stress tests fixes --- .../src/main/java/com/azure/storage/stress/TelemetryHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/storage/azure-storage-stress/src/main/java/com/azure/storage/stress/TelemetryHelper.java b/sdk/storage/azure-storage-stress/src/main/java/com/azure/storage/stress/TelemetryHelper.java index b633479100b1..4bf6e523eae1 100644 --- a/sdk/storage/azure-storage-stress/src/main/java/com/azure/storage/stress/TelemetryHelper.java +++ b/sdk/storage/azure-storage-stress/src/main/java/com/azure/storage/stress/TelemetryHelper.java @@ -124,7 +124,7 @@ public String getDescription() { Cpu.registerObservers(otel); MemoryPools.registerObservers(otel); Threads.registerObservers(otel); - GarbageCollector.registerObservers(otel, true); + GarbageCollector.registerObservers(otel); OpenTelemetryAppender.install(otel); return otel; } From 0c0226b1aa1041be2ffe32ddf6f8d59aca039341 Mon Sep 17 00:00:00 2001 From: browndav Date: Fri, 1 May 2026 13:21:38 -0400 Subject: [PATCH 06/11] Fix storage stress fault injector certificate trust Export the fault injector certificate as PEM and wait for it before importing it into the Java truststore so storage stress tests fail fast instead of hitting PKIX errors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../scripts/fault-injector.sh | 2 +- .../scripts/stress-run.sh | 12 +++++++++++- .../templates/stress-test-job.yaml | 14 ++++++++++++-- .../templates/stress-test-job.yaml | 14 ++++++++++++-- .../templates/stress-test-job.yaml | 14 ++++++++++++-- 5 files changed, 48 insertions(+), 8 deletions(-) diff --git a/sdk/storage/azure-storage-blob-stress/scripts/fault-injector.sh b/sdk/storage/azure-storage-blob-stress/scripts/fault-injector.sh index ed834fc131d6..2d6b39967ab1 100644 --- a/sdk/storage/azure-storage-blob-stress/scripts/fault-injector.sh +++ b/sdk/storage/azure-storage-blob-stress/scripts/fault-injector.sh @@ -1,4 +1,4 @@ #!/bin/sh set -ex; -dotnet dev-certs https --export-path /mnt/outputs/dev-cert.pfx; +dotnet dev-certs https --export-path /mnt/outputs/dev-cert.crt --format PEM --no-password; /root/.dotnet/tools/http-fault-injector; diff --git a/sdk/storage/azure-storage-blob-stress/scripts/stress-run.sh b/sdk/storage/azure-storage-blob-stress/scripts/stress-run.sh index 20a7669c46e9..7604926a6782 100644 --- a/sdk/storage/azure-storage-blob-stress/scripts/stress-run.sh +++ b/sdk/storage/azure-storage-blob-stress/scripts/stress-run.sh @@ -1,4 +1,14 @@ #!/bin/sh set -ex; set -exa; -keytool -import -alias test -file /mnt/outputs/dev-cert.pfx -keystore ${JAVA_HOME}/lib/security/cacerts -noprompt -keypass changeit -storepass changeit; +attempts=0; +while [ ! -s /mnt/outputs/dev-cert.crt ]; do + attempts=$((attempts + 1)); + if [ "$attempts" -gt 60 ]; then + echo "Timed out waiting for fault injector certificate" >&2; + exit 1; + fi; + sleep 1; +done; +keytool -delete -alias HttpFaultInject -keystore "${JAVA_HOME}/lib/security/cacerts" -storepass changeit >/dev/null 2>&1 || true; +keytool -importcert -trustcacerts -alias HttpFaultInject -file /mnt/outputs/dev-cert.crt -keystore "${JAVA_HOME}/lib/security/cacerts" -noprompt -storepass changeit; diff --git a/sdk/storage/azure-storage-blob-stress/templates/stress-test-job.yaml b/sdk/storage/azure-storage-blob-stress/templates/stress-test-job.yaml index 2a22302d24ed..03a0609beebd 100644 --- a/sdk/storage/azure-storage-blob-stress/templates/stress-test-job.yaml +++ b/sdk/storage/azure-storage-blob-stress/templates/stress-test-job.yaml @@ -16,7 +16,7 @@ spec: args: - | set -ex; - dotnet dev-certs https --export-path /mnt/outputs/dev-cert.pfx; + dotnet dev-certs https --export-path /mnt/outputs/dev-cert.crt --format PEM --no-password; /root/.dotnet/tools/http-fault-injector; resources: limits: @@ -30,7 +30,17 @@ spec: - | set -xa; set -o pipefail; - keytool -import -alias test -file /mnt/outputs/dev-cert.pfx -keystore ${JAVA_HOME}/lib/security/cacerts -noprompt -keypass changeit -storepass changeit; + attempts=0; + while [ ! -s /mnt/outputs/dev-cert.crt ]; do + attempts=$((attempts + 1)); + if [ "$attempts" -gt 60 ]; then + echo "Timed out waiting for fault injector certificate" >&2; + exit 1; + fi; + sleep 1; + done; + keytool -delete -alias HttpFaultInject -keystore "${JAVA_HOME}/lib/security/cacerts" -storepass changeit >/dev/null 2>&1 || true; + keytool -importcert -trustcacerts -alias HttpFaultInject -file /mnt/outputs/dev-cert.crt -keystore "${JAVA_HOME}/lib/security/cacerts" -noprompt -storepass changeit || exit 1; mkdir -p "$DEBUG_SHARE"; . /mnt/outputs/.env; export AZURE_HTTP_CLIENT_IMPLEMENTATION=com.azure.core.http.netty.NettyAsyncHttpClientProvider; diff --git a/sdk/storage/azure-storage-file-datalake-stress/templates/stress-test-job.yaml b/sdk/storage/azure-storage-file-datalake-stress/templates/stress-test-job.yaml index e86f52638947..c10c4ded6d64 100644 --- a/sdk/storage/azure-storage-file-datalake-stress/templates/stress-test-job.yaml +++ b/sdk/storage/azure-storage-file-datalake-stress/templates/stress-test-job.yaml @@ -16,7 +16,7 @@ spec: args: - | set -ex; - dotnet dev-certs https --export-path /mnt/outputs/dev-cert.pfx; + dotnet dev-certs https --export-path /mnt/outputs/dev-cert.crt --format PEM --no-password; /root/.dotnet/tools/http-fault-injector; resources: limits: @@ -30,7 +30,17 @@ spec: - | set -xa; set -o pipefail; - keytool -import -alias test -file /mnt/outputs/dev-cert.pfx -keystore ${JAVA_HOME}/lib/security/cacerts -noprompt -keypass changeit -storepass changeit; + attempts=0; + while [ ! -s /mnt/outputs/dev-cert.crt ]; do + attempts=$((attempts + 1)); + if [ "$attempts" -gt 60 ]; then + echo "Timed out waiting for fault injector certificate" >&2; + exit 1; + fi; + sleep 1; + done; + keytool -delete -alias HttpFaultInject -keystore "${JAVA_HOME}/lib/security/cacerts" -storepass changeit >/dev/null 2>&1 || true; + keytool -importcert -trustcacerts -alias HttpFaultInject -file /mnt/outputs/dev-cert.crt -keystore "${JAVA_HOME}/lib/security/cacerts" -noprompt -storepass changeit || exit 1; mkdir -p "$DEBUG_SHARE"; . /mnt/outputs/.env; export AZURE_HTTP_CLIENT_IMPLEMENTATION=com.azure.core.http.netty.NettyAsyncHttpClientProvider; diff --git a/sdk/storage/azure-storage-file-share-stress/templates/stress-test-job.yaml b/sdk/storage/azure-storage-file-share-stress/templates/stress-test-job.yaml index b558feecdcb8..fe77e9cb6f63 100644 --- a/sdk/storage/azure-storage-file-share-stress/templates/stress-test-job.yaml +++ b/sdk/storage/azure-storage-file-share-stress/templates/stress-test-job.yaml @@ -16,7 +16,7 @@ spec: args: - | set -ex; - dotnet dev-certs https --export-path /mnt/outputs/dev-cert.pfx; + dotnet dev-certs https --export-path /mnt/outputs/dev-cert.crt --format PEM --no-password; /root/.dotnet/tools/http-fault-injector; resources: limits: @@ -30,7 +30,17 @@ spec: - | set -xa; set -o pipefail; - keytool -import -alias test -file /mnt/outputs/dev-cert.pfx -keystore ${JAVA_HOME}/lib/security/cacerts -noprompt -keypass changeit -storepass changeit; + attempts=0; + while [ ! -s /mnt/outputs/dev-cert.crt ]; do + attempts=$((attempts + 1)); + if [ "$attempts" -gt 60 ]; then + echo "Timed out waiting for fault injector certificate" >&2; + exit 1; + fi; + sleep 1; + done; + keytool -delete -alias HttpFaultInject -keystore "${JAVA_HOME}/lib/security/cacerts" -storepass changeit >/dev/null 2>&1 || true; + keytool -importcert -trustcacerts -alias HttpFaultInject -file /mnt/outputs/dev-cert.crt -keystore "${JAVA_HOME}/lib/security/cacerts" -noprompt -storepass changeit || exit 1; mkdir -p "$DEBUG_SHARE"; . /mnt/outputs/.env; export AZURE_HTTP_CLIENT_IMPLEMENTATION=com.azure.core.http.netty.NettyAsyncHttpClientProvider; From 4df065bb9cda2ea55a8990e65b32baa76e08d698 Mon Sep 17 00:00:00 2001 From: browndav Date: Tue, 12 May 2026 12:05:53 -0400 Subject: [PATCH 07/11] bump opentelemetry version --- sdk/storage/azure-storage-blob-stress/pom.xml | 4 ++-- sdk/storage/azure-storage-file-datalake-stress/pom.xml | 4 ++-- sdk/storage/azure-storage-file-share-stress/pom.xml | 4 ++-- sdk/storage/azure-storage-stress/pom.xml | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/sdk/storage/azure-storage-blob-stress/pom.xml b/sdk/storage/azure-storage-blob-stress/pom.xml index ec72a4c2cf2f..a64ef5b1f1d3 100644 --- a/sdk/storage/azure-storage-blob-stress/pom.xml +++ b/sdk/storage/azure-storage-blob-stress/pom.xml @@ -57,12 +57,12 @@ io.opentelemetry.instrumentation opentelemetry-runtime-telemetry-java8 - 2.15.0-alpha + 2.24.0-alpha io.opentelemetry.instrumentation opentelemetry-logback-appender-1.0 - 2.15.0-alpha + 2.24.0-alpha diff --git a/sdk/storage/azure-storage-file-datalake-stress/pom.xml b/sdk/storage/azure-storage-file-datalake-stress/pom.xml index 4080f9c2098d..dc0b24e03b62 100644 --- a/sdk/storage/azure-storage-file-datalake-stress/pom.xml +++ b/sdk/storage/azure-storage-file-datalake-stress/pom.xml @@ -57,12 +57,12 @@ io.opentelemetry.instrumentation opentelemetry-runtime-telemetry-java8 - 2.15.0-alpha + 2.24.0-alpha io.opentelemetry.instrumentation opentelemetry-logback-appender-1.0 - 2.15.0-alpha + 2.24.0-alpha diff --git a/sdk/storage/azure-storage-file-share-stress/pom.xml b/sdk/storage/azure-storage-file-share-stress/pom.xml index e1730f507b00..fff0eb6173c3 100644 --- a/sdk/storage/azure-storage-file-share-stress/pom.xml +++ b/sdk/storage/azure-storage-file-share-stress/pom.xml @@ -57,12 +57,12 @@ io.opentelemetry.instrumentation opentelemetry-runtime-telemetry-java8 - 2.15.0-alpha + 2.24.0-alpha io.opentelemetry.instrumentation opentelemetry-logback-appender-1.0 - 2.15.0-alpha + 2.24.0-alpha diff --git a/sdk/storage/azure-storage-stress/pom.xml b/sdk/storage/azure-storage-stress/pom.xml index 32028b16e7f3..cf1888ebf045 100644 --- a/sdk/storage/azure-storage-stress/pom.xml +++ b/sdk/storage/azure-storage-stress/pom.xml @@ -52,12 +52,12 @@ io.opentelemetry.instrumentation opentelemetry-runtime-telemetry-java8 - 2.15.0-alpha + 2.24.0-alpha io.opentelemetry.instrumentation opentelemetry-logback-appender-1.0 - 2.15.0-alpha + 2.24.0-alpha com.azure From 2e872719e29b3afb1db10b92d03bae9114c36326 Mon Sep 17 00:00:00 2001 From: browndav Date: Tue, 12 May 2026 12:06:25 -0400 Subject: [PATCH 08/11] add cv tests to App.class --- .../src/main/java/com/azure/storage/blob/stress/App.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/App.java b/sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/App.java index e38bd16791ca..d562bbe51363 100644 --- a/sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/App.java +++ b/sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/App.java @@ -15,6 +15,11 @@ public static void main(String[] args) { BlockBlobOutputStream.class, BlockBlobUpload.class, CommitBlockList.class, + ContentValidationDownloadContent.class, + ContentValidationDownloadStream.class, + ContentValidationDownloadToFile.class, + ContentValidationOpenInputStream.class, + ContentValidationOpenSeekableByteChannelRead.class, DownloadToFile.class, DownloadStream.class, DownloadContent.class, From 4598fac088a4977fbf6470567c011000f2484efa Mon Sep 17 00:00:00 2001 From: browndav Date: Tue, 12 May 2026 13:47:39 -0400 Subject: [PATCH 09/11] add script to delete resource groups --- .../delete-matching-resource-groups.ps1 | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 sdk/storage/azure-storage-stress/delete-matching-resource-groups.ps1 diff --git a/sdk/storage/azure-storage-stress/delete-matching-resource-groups.ps1 b/sdk/storage/azure-storage-stress/delete-matching-resource-groups.ps1 new file mode 100644 index 000000000000..5bb8afa4cb8a --- /dev/null +++ b/sdk/storage/azure-storage-stress/delete-matching-resource-groups.ps1 @@ -0,0 +1,59 @@ +param( + [string] $Alias, + [string] $SubscriptionId, + [switch] $Execute +) + +$ErrorActionPreference = "Stop" + +Get-Command az | Out-Null + +if ($SubscriptionId) { + az account set --subscription $SubscriptionId +} + +$currentSubscription = az account show --query "{name:name, id:id}" --output tsv +Write-Host "Using subscription: $currentSubscription" +Write-Host "Looking for resource groups starting with 'SSS3PT_$Alias'..." + +$resourceGroups = @(az group list ` + --query "[?starts_with(name, 'SSS3PT_$Alias')].name" ` + --output tsv) + +if ($resourceGroups.Count -eq 0) { + Write-Host "No matching resource groups found." + exit 0 +} + +Write-Host "" +Write-Host "Matching resource groups:" +$resourceGroups | ForEach-Object { Write-Host " $_" } + +if (-not $Execute) { + Write-Host "" + Write-Host "Dry run only. To delete these resource groups, run:" + Write-Host " .\delete-matching-resource-groups.ps1 -Execute" + Write-Host "" + Write-Host "To target a specific subscription, run:" + Write-Host " .\delete-matching-resource-groups.ps1 -SubscriptionId -Execute" + exit 0 +} + +Write-Host "" +$confirmation = Read-Host "Type DELETE to permanently delete these resource groups" + +if ($confirmation -ne "DELETE") { + Write-Host "Cancelled." + exit 1 +} + +foreach ($resourceGroup in $resourceGroups) { + Write-Host "Deleting resource group: $resourceGroup" + az group delete ` + --name $resourceGroup ` + --yes ` + --no-wait +} + +Write-Host "" +Write-Host "Delete operations submitted." From 262757ddaee942d474441550678c9ea6c678a12e Mon Sep 17 00:00:00 2001 From: browndav Date: Tue, 12 May 2026 13:53:31 -0400 Subject: [PATCH 10/11] update scenarios matrix with tests for cv --- .../scenarios-matrix.yaml | 174 ++++++++++++++++++ .../templates/stress-test-job.yaml | 1 + 2 files changed, 175 insertions(+) diff --git a/sdk/storage/azure-storage-blob-stress/scenarios-matrix.yaml b/sdk/storage/azure-storage-blob-stress/scenarios-matrix.yaml index 3a8920a67b35..130a81d23571 100644 --- a/sdk/storage/azure-storage-blob-stress/scenarios-matrix.yaml +++ b/sdk/storage/azure-storage-blob-stress/scenarios-matrix.yaml @@ -108,6 +108,180 @@ matrix: durationMin: 60 imageBuildDir: "../../.." + # content validation downloads using BlobDownloadStreamOptions with CRC64 validation + cvdownloadstreamsm: + testScenario: contentvalidationdownloadstream + sync: true + sizeBytes: 1024 + downloadFaults: true + durationMin: 25 + imageBuildDir: "../../.." + + # content validation downloads using BlobDownloadStreamOptions with CRC64 validation and async client + cvdownloadstreamasyncsm: + testScenario: contentvalidationdownloadstream + sync: false + sizeBytes: 1024 + downloadFaults: true + durationMin: 25 + imageBuildDir: "../../.." + + # content validation downloads using BlobDownloadStreamOptions with CRC64 validation and large payload + cvdownloadstreamlg: + testScenario: contentvalidationdownloadstream + sync: true + sizeBytes: "52428800" + downloadFaults: true + durationMin: 60 + imageBuildDir: "../../.." + + # content validation downloads using BlobDownloadStreamOptions with CRC64 validation, async client, and large payload + cvdownloadstreamasynclg: + testScenario: contentvalidationdownloadstream + sync: false + sizeBytes: "52428800" + downloadFaults: true + durationMin: 60 + imageBuildDir: "../../.." + + # content validation downloads using BlobDownloadStreamOptions with AUTO validation + cvdownloadstreamauto: + testScenario: contentvalidationdownloadstream + sync: true + sizeBytes: "10485760" + contentValidationAlgorithm: AUTO + downloadFaults: true + durationMin: 25 + imageBuildDir: "../../.." + + # content validation downloads using BlobDownloadContentOptions with CRC64 validation + cvdownloadcontentsm: + testScenario: contentvalidationdownloadcontent + sync: true + sizeBytes: 1024 + downloadFaults: true + durationMin: 25 + imageBuildDir: "../../.." + + # content validation downloads using BlobDownloadContentOptions with CRC64 validation and async client + cvdownloadcontentasyncsm: + testScenario: contentvalidationdownloadcontent + sync: false + sizeBytes: 1024 + downloadFaults: true + durationMin: 25 + imageBuildDir: "../../.." + + # content validation downloads using BlobDownloadContentOptions with CRC64 validation and large payload + cvdownloadcontentlg: + testScenario: contentvalidationdownloadcontent + sync: true + sizeBytes: "52428800" + downloadFaults: true + durationMin: 60 + imageBuildDir: "../../.." + + # content validation downloads using BlobDownloadContentOptions with CRC64 validation, async client, and large payload + cvdownloadcontentasynclg: + testScenario: contentvalidationdownloadcontent + sync: false + sizeBytes: "52428800" + downloadFaults: true + durationMin: 60 + imageBuildDir: "../../.." + + # content validation downloads using BlobDownloadContentOptions with AUTO validation + cvdownloadcontentauto: + testScenario: contentvalidationdownloadcontent + sync: true + sizeBytes: "10485760" + contentValidationAlgorithm: AUTO + downloadFaults: true + durationMin: 25 + imageBuildDir: "../../.." + + # content validation downloads using BlobDownloadToFileOptions with CRC64 validation + cvdownloadfilesm: + testScenario: contentvalidationdownloadtofile + sync: true + sizeBytes: 1024 + downloadFaults: true + durationMin: 25 + imageBuildDir: "../../.." + + # content validation downloads using BlobDownloadToFileOptions with CRC64 validation and async client + cvdownloadfileasyncsm: + testScenario: contentvalidationdownloadtofile + sync: false + sizeBytes: 1024 + downloadFaults: true + durationMin: 25 + imageBuildDir: "../../.." + + # content validation downloads using BlobDownloadToFileOptions with CRC64 validation and multi-block payload + cvdownloadfilemd: + testScenario: contentvalidationdownloadtofile + sync: true + sizeBytes: "16777216" + downloadFaults: true + durationMin: 60 + imageBuildDir: "../../.." + + # content validation downloads using BlobDownloadToFileOptions with CRC64 validation, async client, and multi-block payload + cvdownloadfileasyncmd: + testScenario: contentvalidationdownloadtofile + sync: false + sizeBytes: "16777216" + downloadFaults: true + durationMin: 60 + imageBuildDir: "../../.." + + # content validation downloads using BlobDownloadToFileOptions with AUTO validation + cvdownloadfileauto: + testScenario: contentvalidationdownloadtofile + sync: true + sizeBytes: "10485760" + contentValidationAlgorithm: AUTO + downloadFaults: true + durationMin: 25 + imageBuildDir: "../../.." + + # content validation downloads using BlobInputStreamOptions with CRC64 validation + cvinputstreamsm: + testScenario: contentvalidationopeninputstream + sync: true + sizeBytes: 1024 + downloadFaults: true + durationMin: 25 + imageBuildDir: "../../.." + + # content validation downloads using BlobInputStreamOptions with CRC64 validation and large payload + cvinputstreamlg: + testScenario: contentvalidationopeninputstream + sync: true + sizeBytes: "52428800" + downloadFaults: true + durationMin: 60 + imageBuildDir: "../../.." + + # content validation downloads using BlobSeekableByteChannelReadOptions with CRC64 validation + cvbytechannelreadsm: + testScenario: contentvalidationopenseekablebytechannelread + sync: true + sizeBytes: 1024 + downloadFaults: true + durationMin: 25 + imageBuildDir: "../../.." + + # content validation downloads using BlobSeekableByteChannelReadOptions with CRC64 validation and large payload + cvbytechannelreadlg: + testScenario: contentvalidationopenseekablebytechannelread + sync: true + sizeBytes: "52428800" + downloadFaults: true + durationMin: 60 + imageBuildDir: "../../.." + # this test uploads 1KB (1024 bytes) to append blob, no chunking appendblocksmall: testScenario: appendblock diff --git a/sdk/storage/azure-storage-blob-stress/templates/stress-test-job.yaml b/sdk/storage/azure-storage-blob-stress/templates/stress-test-job.yaml index 03a0609beebd..31c44b03cc0e 100644 --- a/sdk/storage/azure-storage-blob-stress/templates/stress-test-job.yaml +++ b/sdk/storage/azure-storage-blob-stress/templates/stress-test-job.yaml @@ -63,6 +63,7 @@ spec: {{ ternary "--sync" "" .Stress.sync }} \ {{ ternary "--downloadFaults" "" (default false .Stress.downloadFaults) }} \ {{ ternary "--uploadFaults" "" (default false .Stress.uploadFaults) }} \ + {{ with .Stress.contentValidationAlgorithm }}--contentValidationAlgorithm {{ . }}{{ end }} \ --warmup 0 \ 2>&1 | tee -a "${DEBUG_SHARE}/{{ .Stress.testScenario }}-`date +%s`.log"; code=$?; From eb08f3fa885c13ec09604992ed41f82a4068b3f2 Mon Sep 17 00:00:00 2001 From: Local Merge Date: Tue, 26 May 2026 01:55:22 +0530 Subject: [PATCH 11/11] fixing stress tests --- ...geSeekableByteChannelBlobReadBehavior.java | 8 +- ...eByteChannelBlobReadBehaviorUnitTests.java | 87 +++++++++++++++++++ 2 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/StorageSeekableByteChannelBlobReadBehaviorUnitTests.java diff --git a/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/specialized/StorageSeekableByteChannelBlobReadBehavior.java b/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/specialized/StorageSeekableByteChannelBlobReadBehavior.java index 840dd6dda6be..47ef361690ff 100644 --- a/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/specialized/StorageSeekableByteChannelBlobReadBehavior.java +++ b/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/specialized/StorageSeekableByteChannelBlobReadBehavior.java @@ -11,6 +11,7 @@ import com.azure.storage.blob.models.BlobRange; import com.azure.storage.blob.models.BlobRequestConditions; import com.azure.storage.blob.models.BlobStorageException; +import com.azure.storage.blob.models.DownloadRetryOptions; import com.azure.storage.common.implementation.StorageSeekableByteChannel; import java.io.IOException; @@ -75,7 +76,7 @@ public int read(ByteBuffer dst, long sourceOffset) throws IOException { try (ByteBufferBackedOutputStreamUtil dstStream = new ByteBufferBackedOutputStreamUtil(dst)) { BlobDownloadResponse response = client.downloadStreamWithResponse(dstStream, new BlobRange(sourceOffset, (long) dst.remaining()), - null /*downloadRetryOptions*/, requestConditions, false, null, null); + new DownloadRetryOptions(), requestConditions, false, null, null); resourceLength = CoreUtils.extractSizeFromContentRange(response.getDeserializedHeaders().getContentRange()); return dst.position() - initialPosition; } catch (BlobStorageException e) { @@ -89,6 +90,11 @@ public int read(ByteBuffer dst, long sourceOffset) throws IOException { return sourceOffset < resourceLength ? 0 : -1; } throw LOGGER.logExceptionAsError(e); + } catch (RuntimeException e) { + if (resourceLength > 0 && sourceOffset >= resourceLength && e.getCause() instanceof IOException) { + return -1; + } + throw e; } } diff --git a/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/StorageSeekableByteChannelBlobReadBehaviorUnitTests.java b/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/StorageSeekableByteChannelBlobReadBehaviorUnitTests.java new file mode 100644 index 000000000000..f20e6cc9190c --- /dev/null +++ b/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/StorageSeekableByteChannelBlobReadBehaviorUnitTests.java @@ -0,0 +1,87 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.storage.blob.specialized; + +import com.azure.core.http.HttpHeaders; +import com.azure.storage.blob.models.BlobDownloadAsyncResponse; +import com.azure.storage.blob.models.BlobDownloadHeaders; +import com.azure.storage.blob.models.BlobDownloadResponse; +import com.azure.storage.blob.models.DownloadRetryOptions; +import com.azure.storage.common.implementation.Constants; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.verify; + +public class StorageSeekableByteChannelBlobReadBehaviorUnitTests { + + private BlobDownloadResponse createMockDownloadResponse(String contentRange) { + Map headers = new HashMap<>(); + headers.put("Content-Range", contentRange); + return new BlobDownloadResponse(new BlobDownloadAsyncResponse(null, 206, new HttpHeaders(headers), null, + new BlobDownloadHeaders().setContentRange(contentRange))); + } + + @ParameterizedTest + @MethodSource("truncatedErrorResponseAtEofSupplier") + void readReturnEofWhenErrorResponseTruncatedAtKnownEof(long blobSize) throws IOException { + BlobClientBase client = Mockito.mock(BlobClientBase.class); + RuntimeException reactorWrapped = new RuntimeException(new IOException("connection reset by peer")); + Mockito.when(client.downloadStreamWithResponse(any(), any(), any(), any(), anyBoolean(), any(), any())) + .thenThrow(reactorWrapped); + + StorageSeekableByteChannelBlobReadBehavior behavior + = new StorageSeekableByteChannelBlobReadBehavior(client, ByteBuffer.allocate(0), -1, blobSize, null); + + assertEquals(-1, behavior.read(ByteBuffer.allocate(Constants.KB), blobSize)); + } + + private static Stream truncatedErrorResponseAtEofSupplier() { + return Stream.of(Arguments.of(Constants.KB), Arguments.of(50L * Constants.MB)); + } + + @Test + void readRethrowsRuntimeExceptionWhenNotAtEof() { + BlobClientBase client = Mockito.mock(BlobClientBase.class); + RuntimeException reactorWrapped = new RuntimeException(new IOException("connection reset by peer")); + Mockito.when(client.downloadStreamWithResponse(any(), any(), any(), any(), anyBoolean(), any(), any())) + .thenThrow(reactorWrapped); + + StorageSeekableByteChannelBlobReadBehavior behavior + = new StorageSeekableByteChannelBlobReadBehavior(client, ByteBuffer.allocate(0), -1, Constants.KB, null); + + assertThrows(RuntimeException.class, () -> behavior.read(ByteBuffer.allocate(Constants.KB), 0)); + } + + @Test + void readPassesNonNullDownloadRetryOptionsToClient() throws IOException { + BlobClientBase client = Mockito.mock(BlobClientBase.class); + ArgumentCaptor retryCaptor = ArgumentCaptor.forClass(DownloadRetryOptions.class); + Mockito.when(client.downloadStreamWithResponse(any(), any(), any(), any(), anyBoolean(), any(), any())) + .thenReturn(createMockDownloadResponse("bytes 0-1023/1024")); + + StorageSeekableByteChannelBlobReadBehavior behavior + = new StorageSeekableByteChannelBlobReadBehavior(client, ByteBuffer.allocate(0), -1, Constants.KB, null); + behavior.read(ByteBuffer.allocate(Constants.KB), 0); + + verify(client).downloadStreamWithResponse(any(), any(), retryCaptor.capture(), any(), anyBoolean(), any(), + any()); + assertNotNull(retryCaptor.getValue()); + } +}