From 54ca2892f773b4d2adfda8d430c55e5d75963dd1 Mon Sep 17 00:00:00 2001 From: Gunjan Singh Date: Wed, 29 Apr 2026 19:23:27 +0530 Subject: [PATCH 01/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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()); + } +} From c4f002443269aea0b59fa18f2f4b1014f06104d4 Mon Sep 17 00:00:00 2001 From: Isabelle Date: Tue, 19 May 2026 19:17:06 -0700 Subject: [PATCH 12/26] stageblocksmall scenario adjustment --- .../scripts/fault-injector.sh | 2 +- .../scripts/stress-run.sh | 2 +- .../templates/stress-test-job.yaml | 2 +- .../azure/storage/stress/CrcInputStream.java | 18 ++++++++++++++++++ .../azure/storage/stress/TelemetryHelper.java | 2 +- 5 files changed, 22 insertions(+), 4 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 2d6b39967ab1..65c857d3d7ed 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.crt --format PEM --no-password; -/root/.dotnet/tools/http-fault-injector; +/root/.dotnet/tools/http-fault-injector; \ No newline at end of file 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 7604926a6782..cebbfb4d47f4 100644 --- a/sdk/storage/azure-storage-blob-stress/scripts/stress-run.sh +++ b/sdk/storage/azure-storage-blob-stress/scripts/stress-run.sh @@ -11,4 +11,4 @@ while [ ! -s /mnt/outputs/dev-cert.crt ]; do 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; +keytool -importcert -trustcacerts -alias HttpFaultInject -file /mnt/outputs/dev-cert.crt -keystore "${JAVA_HOME}/lib/security/cacerts" -noprompt -storepass changeit; \ No newline at end of file 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 31c44b03cc0e..e1a73df2c7e7 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 @@ -78,4 +78,4 @@ spec: cpu: "0.7" {{- include "stress-test-addons.container-env" . | nindent 6 }} -{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/sdk/storage/azure-storage-stress/src/main/java/com/azure/storage/stress/CrcInputStream.java b/sdk/storage/azure-storage-stress/src/main/java/com/azure/storage/stress/CrcInputStream.java index 7e48798b30b0..ed1cfc59f60c 100644 --- a/sdk/storage/azure-storage-stress/src/main/java/com/azure/storage/stress/CrcInputStream.java +++ b/sdk/storage/azure-storage-stress/src/main/java/com/azure/storage/stress/CrcInputStream.java @@ -53,6 +53,13 @@ public synchronized int read() throws IOException { head.put((byte) b); } length++; + // Emit as soon as the expected size has been delivered so that consumers (such as the + // Storage SDK upload path) that stop reading once they have the exact number of bytes + // they asked for do not leave the Sinks.One waiting on a never-arriving EOF read. + // Repeat emissions are no-ops because tryEmitValue returns FAIL_TERMINATED. + if (size > 0 && length >= size) { + emitContentInfo(); + } return b; } @@ -69,6 +76,11 @@ public synchronized int read(byte buf[], int off, int len) throws IOException { head.put(buf, off, Math.min(read, head.remaining())); } length += read; + // See note in read(): emit once the consumer has been handed all the bytes it requested + // so the sink is guaranteed to complete even if the consumer never reads past EOF. + if (size > 0 && length >= size) { + emitContentInfo(); + } return read; } @@ -134,6 +146,12 @@ public void close() { inputStream.close(); } catch (IOException e) { throw LOGGER.logExceptionAsError(new UncheckedIOException(e)); + } finally { + // Defensive: terminate the sink so any consumer still waiting on getContentInfo() + // does not hang in cases where the stream was closed before being fully read + // (for example after an upload failure that aborted the request body subscription). + // This is a no-op when the sink has already emitted. + emitContentInfo(); } } 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 937c0b1a340fcd3023bc4f3992be2bcf465c17e9 Mon Sep 17 00:00:00 2001 From: Isabelle Date: Tue, 19 May 2026 23:35:26 -0700 Subject: [PATCH 13/26] wip --- .../azure/storage/stress/CrcInputStream.java | 75 +++++++------------ 1 file changed, 25 insertions(+), 50 deletions(-) diff --git a/sdk/storage/azure-storage-stress/src/main/java/com/azure/storage/stress/CrcInputStream.java b/sdk/storage/azure-storage-stress/src/main/java/com/azure/storage/stress/CrcInputStream.java index ed1cfc59f60c..b0f913667ab4 100644 --- a/sdk/storage/azure-storage-stress/src/main/java/com/azure/storage/stress/CrcInputStream.java +++ b/sdk/storage/azure-storage-stress/src/main/java/com/azure/storage/stress/CrcInputStream.java @@ -10,7 +10,6 @@ import com.azure.perf.test.core.RepeatingInputStream; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import reactor.core.publisher.Sinks; import java.io.IOException; import java.io.InputStream; @@ -20,7 +19,6 @@ public class CrcInputStream extends InputStream { private final static ClientLogger LOGGER = new ClientLogger(CrcInputStream.class); - private final Sinks.One sink = Sinks.one(); private final InputStream inputStream; private final CRC32 crc = new CRC32(); private final ByteBuffer head = ByteBuffer.allocate(1024); @@ -44,7 +42,6 @@ public CrcInputStream(InputStream source) { public synchronized int read() throws IOException { int b = inputStream.read(); if (b < 0) { - emitContentInfo(); return b; } @@ -53,13 +50,6 @@ public synchronized int read() throws IOException { head.put((byte) b); } length++; - // Emit as soon as the expected size has been delivered so that consumers (such as the - // Storage SDK upload path) that stop reading once they have the exact number of bytes - // they asked for do not leave the Sinks.One waiting on a never-arriving EOF read. - // Repeat emissions are no-ops because tryEmitValue returns FAIL_TERMINATED. - if (size > 0 && length >= size) { - emitContentInfo(); - } return b; } @@ -67,7 +57,6 @@ public synchronized int read() throws IOException { public synchronized int read(byte buf[], int off, int len) throws IOException { int read = inputStream.read(buf, off, len); if (read < 0) { - emitContentInfo(); return read; } @@ -76,41 +65,9 @@ public synchronized int read(byte buf[], int off, int len) throws IOException { head.put(buf, off, Math.min(read, head.remaining())); } length += read; - // See note in read(): emit once the consumer has been handed all the bytes it requested - // so the sink is guaranteed to complete even if the consumer never reads past EOF. - if (size > 0 && length >= size) { - emitContentInfo(); - } return read; } - // Uses tryEmitValue instead of emitValue(FAIL_FAST) so that resubscriptions - // (SDK retries, verification passes) don't throw on the second EOF. - private void emitContentInfo() { - String baseErrorMessage = "Failed to emit content because "; - Sinks.EmitResult emitResult = sink.tryEmitValue(new ContentInfo(crc.getValue(), length, head)); - switch (emitResult) { - case OK: - case FAIL_TERMINATED: - // No action needed for successful or already-terminated emissions. - break; - case FAIL_CANCELLED: - throw LOGGER.logExceptionAsError(new RuntimeException(baseErrorMessage + - " the sink was previously interrupted by its consumer: " + emitResult)); - case FAIL_OVERFLOW: - throw LOGGER.logExceptionAsError(new RuntimeException(baseErrorMessage + "the buffer is full: " + emitResult)); - case FAIL_NON_SERIALIZED: - throw LOGGER.logExceptionAsError(new RuntimeException(baseErrorMessage + "two threads called emit at " + - "once: " + emitResult)); - case FAIL_ZERO_SUBSCRIBER: - throw LOGGER.logExceptionAsError(new RuntimeException(baseErrorMessage + "the sink requires a " + - "subscriber:" + emitResult)); - default: - throw LOGGER.logExceptionAsError(new RuntimeException(baseErrorMessage + "unexpected emit result: " - + emitResult)); - } - } - @Override public synchronized void mark(int readLimit) { if (markSupported) { @@ -136,8 +93,32 @@ public boolean markSupported() { return markSupported; } + /** + * Returns a {@link Mono} that, on subscription, captures a snapshot of the stream's + * current CRC, byte count and head buffer. + * + *

The returned Mono is intentionally lazy: it does not wait for EOF or + * for any sink to be signaled. Callers are therefore responsible for subscribing only + * after the stream has been fully consumed (for example, after the SDK upload + * call has returned for synchronous flows, or via {@code .then(data.getContentInfo())} + * for reactive flows). Subscribing before the stream is done will produce a snapshot of + * whatever has been read so far.

+ * + *

This contract avoids the previous design's dependence on the SDK reading past EOF + * (which never happened on known-length uploads and could leave the legacy sink waiting + * indefinitely) and naturally tolerates SDK retries: the snapshot reflects the bytes + * that were actually delivered on the final, successful pass.

+ * + * @return a cold Mono that emits a {@link ContentInfo} snapshot on each subscription. + */ public Mono getContentInfo() { - return sink.asMono(); + return Mono.fromCallable(() -> { + synchronized (this) { + // duplicate() shares the underlying byte[] but gives the caller an independent + // position/limit so subsequent reads on this stream don't perturb the snapshot. + return new ContentInfo(crc.getValue(), length, head.duplicate()); + } + }); } @Override @@ -146,12 +127,6 @@ public void close() { inputStream.close(); } catch (IOException e) { throw LOGGER.logExceptionAsError(new UncheckedIOException(e)); - } finally { - // Defensive: terminate the sink so any consumer still waiting on getContentInfo() - // does not hang in cases where the stream was closed before being fully read - // (for example after an upload failure that aborted the request body subscription). - // This is a no-op when the sink has already emitted. - emitContentInfo(); } } From 99f1da7c1187353d6b08a19e66d67595954bf893 Mon Sep 17 00:00:00 2001 From: Isabelle Date: Wed, 20 May 2026 09:30:52 -0700 Subject: [PATCH 14/26] avoid depending on raw short baseName being available globally --- .../stress-test-resources.bicep | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sdk/storage/azure-storage-blob-stress/stress-test-resources.bicep b/sdk/storage/azure-storage-blob-stress/stress-test-resources.bicep index 671aec65300b..25ebd947a8b7 100644 --- a/sdk/storage/azure-storage-blob-stress/stress-test-resources.bicep +++ b/sdk/storage/azure-storage-blob-stress/stress-test-resources.bicep @@ -1,10 +1,10 @@ param baseName string -param endpointSuffix string = 'core.windows.net' +param endpointSuffix string = environment().suffixes.storage param location string = resourceGroup().location -param storageApiVersion string = '2022-09-01' -var primaryAccountName = '${baseName}' -var pageBlobStorageAccountName = '${baseName}pageblob' +var uniqueSuffix = uniqueString(resourceGroup().id) +var primaryAccountName = '${take(baseName, 11)}${uniqueSuffix}' +var pageBlobStorageAccountName = '${take(baseName, 7)}${uniqueSuffix}page' resource primaryAccount 'Microsoft.Storage/storageAccounts@2022-09-01' = { name: primaryAccountName @@ -26,5 +26,5 @@ resource pageBlobStorageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' = properties: {} } -output STORAGE_ENDPOINT_STRING string = '"https://${primaryAccountName}.blob.core.windows.net"' -output PAGE_BLOB_STORAGE_ENDPOINT_STRING string = '"https://${pageBlobStorageAccountName}.blob.core.windows.net"' +output STORAGE_ENDPOINT_STRING string = '"https://${primaryAccountName}.blob.${endpointSuffix}"' +output PAGE_BLOB_STORAGE_ENDPOINT_STRING string = '"https://${pageBlobStorageAccountName}.blob.${endpointSuffix}"' From 320a876397a9759fa5d2cee08b5a66fe847c2e10 Mon Sep 17 00:00:00 2001 From: Isabelle Date: Wed, 20 May 2026 10:38:55 -0700 Subject: [PATCH 15/26] dep pinning --- sdk/storage/azure-storage-blob-stress/pom.xml | 12 ++++++++++++ sdk/storage/azure-storage-stress/pom.xml | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/sdk/storage/azure-storage-blob-stress/pom.xml b/sdk/storage/azure-storage-blob-stress/pom.xml index a64ef5b1f1d3..92888ec20d0d 100644 --- a/sdk/storage/azure-storage-blob-stress/pom.xml +++ b/sdk/storage/azure-storage-blob-stress/pom.xml @@ -21,6 +21,18 @@ - + + + + io.opentelemetry + opentelemetry-bom + 1.58.0 + pom + import + + + + ch.qos.logback diff --git a/sdk/storage/azure-storage-stress/pom.xml b/sdk/storage/azure-storage-stress/pom.xml index cf1888ebf045..8170d61ed19f 100644 --- a/sdk/storage/azure-storage-stress/pom.xml +++ b/sdk/storage/azure-storage-stress/pom.xml @@ -21,6 +21,18 @@ - + + + + io.opentelemetry + opentelemetry-bom + 1.58.0 + pom + import + + + + ch.qos.logback From 0c3add2d6cc1ee4021edd9240dd16a9a13d5292a Mon Sep 17 00:00:00 2001 From: Isabelle Date: Tue, 26 May 2026 09:21:28 -0700 Subject: [PATCH 16/26] reversion --- .../azure/storage/stress/CrcInputStream.java | 57 +++++++++++-------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/sdk/storage/azure-storage-stress/src/main/java/com/azure/storage/stress/CrcInputStream.java b/sdk/storage/azure-storage-stress/src/main/java/com/azure/storage/stress/CrcInputStream.java index b0f913667ab4..7e48798b30b0 100644 --- a/sdk/storage/azure-storage-stress/src/main/java/com/azure/storage/stress/CrcInputStream.java +++ b/sdk/storage/azure-storage-stress/src/main/java/com/azure/storage/stress/CrcInputStream.java @@ -10,6 +10,7 @@ import com.azure.perf.test.core.RepeatingInputStream; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import reactor.core.publisher.Sinks; import java.io.IOException; import java.io.InputStream; @@ -19,6 +20,7 @@ public class CrcInputStream extends InputStream { private final static ClientLogger LOGGER = new ClientLogger(CrcInputStream.class); + private final Sinks.One sink = Sinks.one(); private final InputStream inputStream; private final CRC32 crc = new CRC32(); private final ByteBuffer head = ByteBuffer.allocate(1024); @@ -42,6 +44,7 @@ public CrcInputStream(InputStream source) { public synchronized int read() throws IOException { int b = inputStream.read(); if (b < 0) { + emitContentInfo(); return b; } @@ -57,6 +60,7 @@ public synchronized int read() throws IOException { public synchronized int read(byte buf[], int off, int len) throws IOException { int read = inputStream.read(buf, off, len); if (read < 0) { + emitContentInfo(); return read; } @@ -68,6 +72,33 @@ public synchronized int read(byte buf[], int off, int len) throws IOException { return read; } + // Uses tryEmitValue instead of emitValue(FAIL_FAST) so that resubscriptions + // (SDK retries, verification passes) don't throw on the second EOF. + private void emitContentInfo() { + String baseErrorMessage = "Failed to emit content because "; + Sinks.EmitResult emitResult = sink.tryEmitValue(new ContentInfo(crc.getValue(), length, head)); + switch (emitResult) { + case OK: + case FAIL_TERMINATED: + // No action needed for successful or already-terminated emissions. + break; + case FAIL_CANCELLED: + throw LOGGER.logExceptionAsError(new RuntimeException(baseErrorMessage + + " the sink was previously interrupted by its consumer: " + emitResult)); + case FAIL_OVERFLOW: + throw LOGGER.logExceptionAsError(new RuntimeException(baseErrorMessage + "the buffer is full: " + emitResult)); + case FAIL_NON_SERIALIZED: + throw LOGGER.logExceptionAsError(new RuntimeException(baseErrorMessage + "two threads called emit at " + + "once: " + emitResult)); + case FAIL_ZERO_SUBSCRIBER: + throw LOGGER.logExceptionAsError(new RuntimeException(baseErrorMessage + "the sink requires a " + + "subscriber:" + emitResult)); + default: + throw LOGGER.logExceptionAsError(new RuntimeException(baseErrorMessage + "unexpected emit result: " + + emitResult)); + } + } + @Override public synchronized void mark(int readLimit) { if (markSupported) { @@ -93,32 +124,8 @@ public boolean markSupported() { return markSupported; } - /** - * Returns a {@link Mono} that, on subscription, captures a snapshot of the stream's - * current CRC, byte count and head buffer. - * - *

The returned Mono is intentionally lazy: it does not wait for EOF or - * for any sink to be signaled. Callers are therefore responsible for subscribing only - * after the stream has been fully consumed (for example, after the SDK upload - * call has returned for synchronous flows, or via {@code .then(data.getContentInfo())} - * for reactive flows). Subscribing before the stream is done will produce a snapshot of - * whatever has been read so far.

- * - *

This contract avoids the previous design's dependence on the SDK reading past EOF - * (which never happened on known-length uploads and could leave the legacy sink waiting - * indefinitely) and naturally tolerates SDK retries: the snapshot reflects the bytes - * that were actually delivered on the final, successful pass.

- * - * @return a cold Mono that emits a {@link ContentInfo} snapshot on each subscription. - */ public Mono getContentInfo() { - return Mono.fromCallable(() -> { - synchronized (this) { - // duplicate() shares the underlying byte[] but gives the caller an independent - // position/limit so subsequent reads on this stream don't perturb the snapshot. - return new ContentInfo(crc.getValue(), length, head.duplicate()); - } - }); + return sink.asMono(); } @Override From 5ebefba9b1e45464110f177b5edba263a4987cb5 Mon Sep 17 00:00:00 2001 From: Isabelle Date: Tue, 26 May 2026 09:24:28 -0700 Subject: [PATCH 17/26] adding back --- .../azure/storage/stress/CrcInputStream.java | 57 ++++++++----------- 1 file changed, 25 insertions(+), 32 deletions(-) diff --git a/sdk/storage/azure-storage-stress/src/main/java/com/azure/storage/stress/CrcInputStream.java b/sdk/storage/azure-storage-stress/src/main/java/com/azure/storage/stress/CrcInputStream.java index 7e48798b30b0..b0f913667ab4 100644 --- a/sdk/storage/azure-storage-stress/src/main/java/com/azure/storage/stress/CrcInputStream.java +++ b/sdk/storage/azure-storage-stress/src/main/java/com/azure/storage/stress/CrcInputStream.java @@ -10,7 +10,6 @@ import com.azure.perf.test.core.RepeatingInputStream; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import reactor.core.publisher.Sinks; import java.io.IOException; import java.io.InputStream; @@ -20,7 +19,6 @@ public class CrcInputStream extends InputStream { private final static ClientLogger LOGGER = new ClientLogger(CrcInputStream.class); - private final Sinks.One sink = Sinks.one(); private final InputStream inputStream; private final CRC32 crc = new CRC32(); private final ByteBuffer head = ByteBuffer.allocate(1024); @@ -44,7 +42,6 @@ public CrcInputStream(InputStream source) { public synchronized int read() throws IOException { int b = inputStream.read(); if (b < 0) { - emitContentInfo(); return b; } @@ -60,7 +57,6 @@ public synchronized int read() throws IOException { public synchronized int read(byte buf[], int off, int len) throws IOException { int read = inputStream.read(buf, off, len); if (read < 0) { - emitContentInfo(); return read; } @@ -72,33 +68,6 @@ public synchronized int read(byte buf[], int off, int len) throws IOException { return read; } - // Uses tryEmitValue instead of emitValue(FAIL_FAST) so that resubscriptions - // (SDK retries, verification passes) don't throw on the second EOF. - private void emitContentInfo() { - String baseErrorMessage = "Failed to emit content because "; - Sinks.EmitResult emitResult = sink.tryEmitValue(new ContentInfo(crc.getValue(), length, head)); - switch (emitResult) { - case OK: - case FAIL_TERMINATED: - // No action needed for successful or already-terminated emissions. - break; - case FAIL_CANCELLED: - throw LOGGER.logExceptionAsError(new RuntimeException(baseErrorMessage + - " the sink was previously interrupted by its consumer: " + emitResult)); - case FAIL_OVERFLOW: - throw LOGGER.logExceptionAsError(new RuntimeException(baseErrorMessage + "the buffer is full: " + emitResult)); - case FAIL_NON_SERIALIZED: - throw LOGGER.logExceptionAsError(new RuntimeException(baseErrorMessage + "two threads called emit at " + - "once: " + emitResult)); - case FAIL_ZERO_SUBSCRIBER: - throw LOGGER.logExceptionAsError(new RuntimeException(baseErrorMessage + "the sink requires a " + - "subscriber:" + emitResult)); - default: - throw LOGGER.logExceptionAsError(new RuntimeException(baseErrorMessage + "unexpected emit result: " - + emitResult)); - } - } - @Override public synchronized void mark(int readLimit) { if (markSupported) { @@ -124,8 +93,32 @@ public boolean markSupported() { return markSupported; } + /** + * Returns a {@link Mono} that, on subscription, captures a snapshot of the stream's + * current CRC, byte count and head buffer. + * + *

The returned Mono is intentionally lazy: it does not wait for EOF or + * for any sink to be signaled. Callers are therefore responsible for subscribing only + * after the stream has been fully consumed (for example, after the SDK upload + * call has returned for synchronous flows, or via {@code .then(data.getContentInfo())} + * for reactive flows). Subscribing before the stream is done will produce a snapshot of + * whatever has been read so far.

+ * + *

This contract avoids the previous design's dependence on the SDK reading past EOF + * (which never happened on known-length uploads and could leave the legacy sink waiting + * indefinitely) and naturally tolerates SDK retries: the snapshot reflects the bytes + * that were actually delivered on the final, successful pass.

+ * + * @return a cold Mono that emits a {@link ContentInfo} snapshot on each subscription. + */ public Mono getContentInfo() { - return sink.asMono(); + return Mono.fromCallable(() -> { + synchronized (this) { + // duplicate() shares the underlying byte[] but gives the caller an independent + // position/limit so subsequent reads on this stream don't perturb the snapshot. + return new ContentInfo(crc.getValue(), length, head.duplicate()); + } + }); } @Override From 490b22ea1bea9cc108a227a96bb67c8d48152ac3 Mon Sep 17 00:00:00 2001 From: Isabelle Date: Tue, 26 May 2026 17:59:37 -0700 Subject: [PATCH 18/26] wip --- .../perf/test/core/PerfStressProgram.java | 13 +++ .../storage/blob/stress/BlobScenarioBase.java | 81 +++++++++++++++++-- 2 files changed, 87 insertions(+), 7 deletions(-) diff --git a/common/perf-test-core/src/main/java/com/azure/perf/test/core/PerfStressProgram.java b/common/perf-test-core/src/main/java/com/azure/perf/test/core/PerfStressProgram.java index 66eae27f7f3e..5602b9b94f58 100644 --- a/common/perf-test-core/src/main/java/com/azure/perf/test/core/PerfStressProgram.java +++ b/common/perf-test-core/src/main/java/com/azure/perf/test/core/PerfStressProgram.java @@ -348,8 +348,21 @@ public static void runTests(PerfTestBase[] tests, boolean sync, boolean compl .block(); } } catch (Exception e) { + // Previously this catch swallowed the exception, printed a stack trace, and let the + // method fall through to "=== Results ===" / globalCleanupAsync, which caused the + // process to exit 0. That made real test failures (e.g. an SDK NullPointerException + // surfaced by a retry path during a stress run) invisible to CI and dashboards: the + // Kubernetes Job reported Succeeded and the workbook showed a healthy success ratio + // even though the test loop had aborted partway through its configured duration. + // + // Rethrow as an unchecked exception so the surrounding lifecycle still runs the + // `finally` block below (progressStatus.cancel()) and the outer `try { ... } finally` + // in run(...) still drives globalCleanupAsync, but the JVM ultimately exits non-zero + // and the failure is visible. See sdk/storage/BUG-payload-size-gate-npe.md for the + // specific case that motivated this change. System.err.println("Error occurred running tests: " + System.lineSeparator() + e); e.printStackTrace(System.err); + throw (e instanceof RuntimeException) ? (RuntimeException) e : new RuntimeException(e); } finally { progressStatus.cancel(); } diff --git a/sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/BlobScenarioBase.java b/sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/BlobScenarioBase.java index 6bf3dc004108..9ce4a8cbdb94 100644 --- a/sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/BlobScenarioBase.java +++ b/sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/BlobScenarioBase.java @@ -15,12 +15,10 @@ import com.azure.storage.blob.BlobServiceAsyncClient; import com.azure.storage.blob.BlobServiceClient; import com.azure.storage.blob.BlobServiceClientBuilder; -import com.azure.storage.stress.ContentMismatchException; import com.azure.storage.stress.TelemetryHelper; import com.azure.storage.stress.FaultInjectionProbabilities; import com.azure.storage.stress.FaultInjectingHttpPolicy; import com.azure.storage.stress.StorageStressOptions; -import reactor.core.Exceptions; import reactor.core.publisher.Mono; import java.time.Instant; @@ -156,20 +154,89 @@ public void run() { @SuppressWarnings("try") @Override public Mono runAsync() { + // We previously wrapped runInternalAsync with an unconditional `.retryWhen(Retry.max(3))`. + // That mask hid a real liveness bug in the SDK upload pipeline: when an HTTP fault + // (especially the request-side `pq*` indefinite variants from FaultInjectingHttpPolicy) + // causes one parallel rail's Mono to stop making progress, the unconditional retry burns + // 3 attempts of 60s Netty response timeouts on top of an already-stuck pipeline and the + // hang propagates outward. With three parallel rails, a single such stall freezes the + // whole `flatMap(runTestAsync, 1)` for the remainder of the configured --duration, which + // is exactly what the six "Failed" large-payload pods exhibited on 2026-05-26 (see + // sdk/storage/BUG-blob-upload-hang-on-fault.md). + // + // Replace the retry with an outer per-operation timeout. Any single iteration that + // doesn't complete within `OPERATION_TIMEOUT` fails fast as a TimeoutException, which: + // 1. lets the rail recycle and start the next iteration, + // 2. surfaces a real failure on the dashboard instead of a frozen progress counter, + // 3. produces evidence (the TimeoutException) for SDK bug triage, and + // 4. still tolerates the natural per-op latency under fault injection (~6.3% of HTTP + // calls are configured-indefinite; effective avg op latency is a few seconds for + // small payloads and tens of seconds for large multi-request operations, well under + // the 2-minute cap below). + // If a scenario legitimately needs longer than the default per op, override + // `getOperationTimeout()` in the scenario class rather than blanket-raising it here. + // + // Tuning note (2026-05-26): the previous default was 2 minutes, which was too coarse. + // Under fault injection the dominant wedge is the `pq*` (request-side, indefinite) + // variant. Netty's `responseTimeout` already fires after 60s, so any operation still + // unresponsive ~30s after that is a real liveness bug and we want to fail fast and + // recycle the rail. Empirically this 4-6x's small-scenario throughput (small-blob ops + // typically complete in <2s; even fault-injected ops are bounded by the 60s Netty + // response timeout) while still leaving plenty of headroom for multi-block large + // operations via per-scenario overrides. + // + // CRITICAL: the outer `.onErrorResume(e -> Mono.empty())` converts a failed iteration + // into a successful "this iteration is done" signal *after* `instrumentRunAsync` has + // already fired its `doOnError` side-effect (which calls `trackFailure` and increments + // the `failed_runs` metric). Without this, a TimeoutException -- or any other error -- + // propagates out of `runTestAsync()` into `flatMap(runTestAsync, 1)` in + // ApiPerfTestBase.runAllAsync and terminates the entire Flux, cancelling all parallel + // rails and aborting the test loop. The previous `.retryWhen(Retry.max(3))` happened to + // mask this because most ops succeeded within retries, so the propagation path was + // rarely exercised; with the retry gone, every long-tail op would otherwise kill the + // whole job after the first 2-minute timeout. The error has already been logged and + // counted as a failure by the time `onErrorResume` runs, so no information is lost. return telemetryHelper.instrumentRunAsync(ctx -> runInternalAsync(ctx) - .retryWhen(reactor.util.retry.Retry.max(3) - .filter(e -> !(Exceptions.unwrap(e) - instanceof ContentMismatchException))) + .timeout(getOperationTimeout()) .doOnError(e -> { // Log the error for debugging but let legitimate failures propagate LOGGER.atError() .addKeyValue("error", e.getMessage()) .addKeyValue("errorType", e.getClass().getSimpleName()) - .log("Test operation failed after retries"); - })); + .log("Test operation failed"); + })) + .onErrorResume(e -> Mono.empty()); } + /** + * Default per-operation timeout for stress scenarios. Small-payload scenarios complete + * well under 2s in steady state and are bounded by Netty's 60s `responseTimeout` under + * fault injection, so 30s catches genuine liveness wedges without throwing away healthy + * long-tail ops. Multi-block large-payload variants need more time (a single op can be + * many sequential block uploads), so we scale the default up based on `options.getSize()`: + * one extra minute per 16 MiB above 1 MiB, capped at 5 minutes. Scenarios that need an + * even larger envelope can override this method: + * + *
{@code
+     * @Override
+     * protected Duration getOperationTimeout() { return Duration.ofMinutes(10); }
+     * }
+ */ + protected Duration getOperationTimeout() { + long sizeBytes = options.getSize(); + // Baseline 30s, +60s per 16 MiB beyond the first 1 MiB. Capped at 5 minutes so a + // genuinely-wedged rail still recovers promptly even for the largest configured payloads. + long extraSeconds = Math.max(0, (sizeBytes - SMALL_PAYLOAD_THRESHOLD_BYTES) / BYTES_PER_EXTRA_MINUTE) * 60; + long totalSeconds = Math.min(BASE_TIMEOUT_SECONDS + extraSeconds, MAX_TIMEOUT_SECONDS); + return Duration.ofSeconds(totalSeconds); + } + + private static final long SMALL_PAYLOAD_THRESHOLD_BYTES = (long) 1024 * 1024; // 1 MiB + private static final long BYTES_PER_EXTRA_MINUTE = 16L * 1024 * 1024; // 16 MiB + private static final long BASE_TIMEOUT_SECONDS = 30; + private static final long MAX_TIMEOUT_SECONDS = 5 * 60; + protected abstract void runInternal(Context context) throws Exception; protected abstract Mono runInternalAsync(Context context); From 0052ecb66a58fe4e83d81b28a755c708e479663a Mon Sep 17 00:00:00 2001 From: Isabelle Date: Thu, 28 May 2026 22:29:39 -0700 Subject: [PATCH 19/26] storageseekablebytechannel change From b8a1e55d560bee598414785fd6c90b4d5a05f7e5 Mon Sep 17 00:00:00 2001 From: Isabelle Date: Sun, 31 May 2026 10:29:35 -0700 Subject: [PATCH 20/26] bare minimum changes --- .../storage/blob/stress/BlobScenarioBase.java | 81 ++----------------- ...geSeekableByteChannelBlobReadBehavior.java | 5 -- .../azure/storage/stress/CrcInputStream.java | 57 +++++++------ 3 files changed, 39 insertions(+), 104 deletions(-) diff --git a/sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/BlobScenarioBase.java b/sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/BlobScenarioBase.java index 9ce4a8cbdb94..6bf3dc004108 100644 --- a/sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/BlobScenarioBase.java +++ b/sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/BlobScenarioBase.java @@ -15,10 +15,12 @@ import com.azure.storage.blob.BlobServiceAsyncClient; import com.azure.storage.blob.BlobServiceClient; import com.azure.storage.blob.BlobServiceClientBuilder; +import com.azure.storage.stress.ContentMismatchException; import com.azure.storage.stress.TelemetryHelper; import com.azure.storage.stress.FaultInjectionProbabilities; import com.azure.storage.stress.FaultInjectingHttpPolicy; import com.azure.storage.stress.StorageStressOptions; +import reactor.core.Exceptions; import reactor.core.publisher.Mono; import java.time.Instant; @@ -154,89 +156,20 @@ public void run() { @SuppressWarnings("try") @Override public Mono runAsync() { - // We previously wrapped runInternalAsync with an unconditional `.retryWhen(Retry.max(3))`. - // That mask hid a real liveness bug in the SDK upload pipeline: when an HTTP fault - // (especially the request-side `pq*` indefinite variants from FaultInjectingHttpPolicy) - // causes one parallel rail's Mono to stop making progress, the unconditional retry burns - // 3 attempts of 60s Netty response timeouts on top of an already-stuck pipeline and the - // hang propagates outward. With three parallel rails, a single such stall freezes the - // whole `flatMap(runTestAsync, 1)` for the remainder of the configured --duration, which - // is exactly what the six "Failed" large-payload pods exhibited on 2026-05-26 (see - // sdk/storage/BUG-blob-upload-hang-on-fault.md). - // - // Replace the retry with an outer per-operation timeout. Any single iteration that - // doesn't complete within `OPERATION_TIMEOUT` fails fast as a TimeoutException, which: - // 1. lets the rail recycle and start the next iteration, - // 2. surfaces a real failure on the dashboard instead of a frozen progress counter, - // 3. produces evidence (the TimeoutException) for SDK bug triage, and - // 4. still tolerates the natural per-op latency under fault injection (~6.3% of HTTP - // calls are configured-indefinite; effective avg op latency is a few seconds for - // small payloads and tens of seconds for large multi-request operations, well under - // the 2-minute cap below). - // If a scenario legitimately needs longer than the default per op, override - // `getOperationTimeout()` in the scenario class rather than blanket-raising it here. - // - // Tuning note (2026-05-26): the previous default was 2 minutes, which was too coarse. - // Under fault injection the dominant wedge is the `pq*` (request-side, indefinite) - // variant. Netty's `responseTimeout` already fires after 60s, so any operation still - // unresponsive ~30s after that is a real liveness bug and we want to fail fast and - // recycle the rail. Empirically this 4-6x's small-scenario throughput (small-blob ops - // typically complete in <2s; even fault-injected ops are bounded by the 60s Netty - // response timeout) while still leaving plenty of headroom for multi-block large - // operations via per-scenario overrides. - // - // CRITICAL: the outer `.onErrorResume(e -> Mono.empty())` converts a failed iteration - // into a successful "this iteration is done" signal *after* `instrumentRunAsync` has - // already fired its `doOnError` side-effect (which calls `trackFailure` and increments - // the `failed_runs` metric). Without this, a TimeoutException -- or any other error -- - // propagates out of `runTestAsync()` into `flatMap(runTestAsync, 1)` in - // ApiPerfTestBase.runAllAsync and terminates the entire Flux, cancelling all parallel - // rails and aborting the test loop. The previous `.retryWhen(Retry.max(3))` happened to - // mask this because most ops succeeded within retries, so the propagation path was - // rarely exercised; with the retry gone, every long-tail op would otherwise kill the - // whole job after the first 2-minute timeout. The error has already been logged and - // counted as a failure by the time `onErrorResume` runs, so no information is lost. return telemetryHelper.instrumentRunAsync(ctx -> runInternalAsync(ctx) - .timeout(getOperationTimeout()) + .retryWhen(reactor.util.retry.Retry.max(3) + .filter(e -> !(Exceptions.unwrap(e) + instanceof ContentMismatchException))) .doOnError(e -> { // Log the error for debugging but let legitimate failures propagate LOGGER.atError() .addKeyValue("error", e.getMessage()) .addKeyValue("errorType", e.getClass().getSimpleName()) - .log("Test operation failed"); - })) - .onErrorResume(e -> Mono.empty()); + .log("Test operation failed after retries"); + })); } - /** - * Default per-operation timeout for stress scenarios. Small-payload scenarios complete - * well under 2s in steady state and are bounded by Netty's 60s `responseTimeout` under - * fault injection, so 30s catches genuine liveness wedges without throwing away healthy - * long-tail ops. Multi-block large-payload variants need more time (a single op can be - * many sequential block uploads), so we scale the default up based on `options.getSize()`: - * one extra minute per 16 MiB above 1 MiB, capped at 5 minutes. Scenarios that need an - * even larger envelope can override this method: - * - *
{@code
-     * @Override
-     * protected Duration getOperationTimeout() { return Duration.ofMinutes(10); }
-     * }
- */ - protected Duration getOperationTimeout() { - long sizeBytes = options.getSize(); - // Baseline 30s, +60s per 16 MiB beyond the first 1 MiB. Capped at 5 minutes so a - // genuinely-wedged rail still recovers promptly even for the largest configured payloads. - long extraSeconds = Math.max(0, (sizeBytes - SMALL_PAYLOAD_THRESHOLD_BYTES) / BYTES_PER_EXTRA_MINUTE) * 60; - long totalSeconds = Math.min(BASE_TIMEOUT_SECONDS + extraSeconds, MAX_TIMEOUT_SECONDS); - return Duration.ofSeconds(totalSeconds); - } - - private static final long SMALL_PAYLOAD_THRESHOLD_BYTES = (long) 1024 * 1024; // 1 MiB - private static final long BYTES_PER_EXTRA_MINUTE = 16L * 1024 * 1024; // 16 MiB - private static final long BASE_TIMEOUT_SECONDS = 30; - private static final long MAX_TIMEOUT_SECONDS = 5 * 60; - protected abstract void runInternal(Context context) throws Exception; protected abstract Mono runInternalAsync(Context context); 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 47ef361690ff..5700e1e7dd36 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 @@ -90,11 +90,6 @@ 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-stress/src/main/java/com/azure/storage/stress/CrcInputStream.java b/sdk/storage/azure-storage-stress/src/main/java/com/azure/storage/stress/CrcInputStream.java index b0f913667ab4..7e48798b30b0 100644 --- a/sdk/storage/azure-storage-stress/src/main/java/com/azure/storage/stress/CrcInputStream.java +++ b/sdk/storage/azure-storage-stress/src/main/java/com/azure/storage/stress/CrcInputStream.java @@ -10,6 +10,7 @@ import com.azure.perf.test.core.RepeatingInputStream; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import reactor.core.publisher.Sinks; import java.io.IOException; import java.io.InputStream; @@ -19,6 +20,7 @@ public class CrcInputStream extends InputStream { private final static ClientLogger LOGGER = new ClientLogger(CrcInputStream.class); + private final Sinks.One sink = Sinks.one(); private final InputStream inputStream; private final CRC32 crc = new CRC32(); private final ByteBuffer head = ByteBuffer.allocate(1024); @@ -42,6 +44,7 @@ public CrcInputStream(InputStream source) { public synchronized int read() throws IOException { int b = inputStream.read(); if (b < 0) { + emitContentInfo(); return b; } @@ -57,6 +60,7 @@ public synchronized int read() throws IOException { public synchronized int read(byte buf[], int off, int len) throws IOException { int read = inputStream.read(buf, off, len); if (read < 0) { + emitContentInfo(); return read; } @@ -68,6 +72,33 @@ public synchronized int read(byte buf[], int off, int len) throws IOException { return read; } + // Uses tryEmitValue instead of emitValue(FAIL_FAST) so that resubscriptions + // (SDK retries, verification passes) don't throw on the second EOF. + private void emitContentInfo() { + String baseErrorMessage = "Failed to emit content because "; + Sinks.EmitResult emitResult = sink.tryEmitValue(new ContentInfo(crc.getValue(), length, head)); + switch (emitResult) { + case OK: + case FAIL_TERMINATED: + // No action needed for successful or already-terminated emissions. + break; + case FAIL_CANCELLED: + throw LOGGER.logExceptionAsError(new RuntimeException(baseErrorMessage + + " the sink was previously interrupted by its consumer: " + emitResult)); + case FAIL_OVERFLOW: + throw LOGGER.logExceptionAsError(new RuntimeException(baseErrorMessage + "the buffer is full: " + emitResult)); + case FAIL_NON_SERIALIZED: + throw LOGGER.logExceptionAsError(new RuntimeException(baseErrorMessage + "two threads called emit at " + + "once: " + emitResult)); + case FAIL_ZERO_SUBSCRIBER: + throw LOGGER.logExceptionAsError(new RuntimeException(baseErrorMessage + "the sink requires a " + + "subscriber:" + emitResult)); + default: + throw LOGGER.logExceptionAsError(new RuntimeException(baseErrorMessage + "unexpected emit result: " + + emitResult)); + } + } + @Override public synchronized void mark(int readLimit) { if (markSupported) { @@ -93,32 +124,8 @@ public boolean markSupported() { return markSupported; } - /** - * Returns a {@link Mono} that, on subscription, captures a snapshot of the stream's - * current CRC, byte count and head buffer. - * - *

The returned Mono is intentionally lazy: it does not wait for EOF or - * for any sink to be signaled. Callers are therefore responsible for subscribing only - * after the stream has been fully consumed (for example, after the SDK upload - * call has returned for synchronous flows, or via {@code .then(data.getContentInfo())} - * for reactive flows). Subscribing before the stream is done will produce a snapshot of - * whatever has been read so far.

- * - *

This contract avoids the previous design's dependence on the SDK reading past EOF - * (which never happened on known-length uploads and could leave the legacy sink waiting - * indefinitely) and naturally tolerates SDK retries: the snapshot reflects the bytes - * that were actually delivered on the final, successful pass.

- * - * @return a cold Mono that emits a {@link ContentInfo} snapshot on each subscription. - */ public Mono getContentInfo() { - return Mono.fromCallable(() -> { - synchronized (this) { - // duplicate() shares the underlying byte[] but gives the caller an independent - // position/limit so subsequent reads on this stream don't perturb the snapshot. - return new ContentInfo(crc.getValue(), length, head.duplicate()); - } - }); + return sink.asMono(); } @Override From 635064f694a7dddea39fcf4dab8d5d2552b1c04f Mon Sep 17 00:00:00 2001 From: Isabelle Date: Sun, 31 May 2026 15:21:04 -0700 Subject: [PATCH 21/26] removing perf core change --- .../com/azure/perf/test/core/PerfStressProgram.java | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/common/perf-test-core/src/main/java/com/azure/perf/test/core/PerfStressProgram.java b/common/perf-test-core/src/main/java/com/azure/perf/test/core/PerfStressProgram.java index 5602b9b94f58..66eae27f7f3e 100644 --- a/common/perf-test-core/src/main/java/com/azure/perf/test/core/PerfStressProgram.java +++ b/common/perf-test-core/src/main/java/com/azure/perf/test/core/PerfStressProgram.java @@ -348,21 +348,8 @@ public static void runTests(PerfTestBase[] tests, boolean sync, boolean compl .block(); } } catch (Exception e) { - // Previously this catch swallowed the exception, printed a stack trace, and let the - // method fall through to "=== Results ===" / globalCleanupAsync, which caused the - // process to exit 0. That made real test failures (e.g. an SDK NullPointerException - // surfaced by a retry path during a stress run) invisible to CI and dashboards: the - // Kubernetes Job reported Succeeded and the workbook showed a healthy success ratio - // even though the test loop had aborted partway through its configured duration. - // - // Rethrow as an unchecked exception so the surrounding lifecycle still runs the - // `finally` block below (progressStatus.cancel()) and the outer `try { ... } finally` - // in run(...) still drives globalCleanupAsync, but the JVM ultimately exits non-zero - // and the failure is visible. See sdk/storage/BUG-payload-size-gate-npe.md for the - // specific case that motivated this change. System.err.println("Error occurred running tests: " + System.lineSeparator() + e); e.printStackTrace(System.err); - throw (e instanceof RuntimeException) ? (RuntimeException) e : new RuntimeException(e); } finally { progressStatus.cancel(); } From 593e0e55c5e996c8b4bb92e53a4f9c5672ca2ab2 Mon Sep 17 00:00:00 2001 From: Isabelle Date: Mon, 1 Jun 2026 09:21:36 -0700 Subject: [PATCH 22/26] removing redundant test file and non-blob package changes --- ...eByteChannelBlobReadBehaviorUnitTests.java | 87 ------------------- .../delete-matching-resource-groups.ps1 | 59 ------------- 2 files changed, 146 deletions(-) delete mode 100644 sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/StorageSeekableByteChannelBlobReadBehaviorUnitTests.java delete mode 100644 sdk/storage/azure-storage-stress/delete-matching-resource-groups.ps1 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 deleted file mode 100644 index f20e6cc9190c..000000000000 --- a/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/StorageSeekableByteChannelBlobReadBehaviorUnitTests.java +++ /dev/null @@ -1,87 +0,0 @@ -// 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()); - } -} diff --git a/sdk/storage/azure-storage-stress/delete-matching-resource-groups.ps1 b/sdk/storage/azure-storage-stress/delete-matching-resource-groups.ps1 deleted file mode 100644 index 5bb8afa4cb8a..000000000000 --- a/sdk/storage/azure-storage-stress/delete-matching-resource-groups.ps1 +++ /dev/null @@ -1,59 +0,0 @@ -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 5dc033d5fe7cbd8aa9d405b725773c607186fa12 Mon Sep 17 00:00:00 2001 From: Isabelle Date: Mon, 1 Jun 2026 09:29:33 -0700 Subject: [PATCH 23/26] removing redundant test file and non-blob package changes --- ...ekableByteChannelBlobReadBehaviorTests.java | 18 ++++++++++++++++++ .../templates/stress-test-job.yaml | 14 ++------------ .../templates/stress-test-job.yaml | 14 ++------------ 3 files changed, 22 insertions(+), 24 deletions(-) diff --git a/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/StorageSeekableByteChannelBlobReadBehaviorTests.java b/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/StorageSeekableByteChannelBlobReadBehaviorTests.java index 5ccc9dfc1ea4..d0333c057ef8 100644 --- a/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/StorageSeekableByteChannelBlobReadBehaviorTests.java +++ b/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/StorageSeekableByteChannelBlobReadBehaviorTests.java @@ -12,6 +12,7 @@ import com.azure.storage.blob.models.BlobDownloadResponse; import com.azure.storage.blob.models.BlobRange; import com.azure.storage.blob.models.BlobRequestConditions; +import com.azure.storage.blob.models.DownloadRetryOptions; import com.azure.storage.blob.models.PageRange; import com.azure.storage.common.implementation.Constants; import org.junit.jupiter.api.AfterEach; @@ -35,6 +36,7 @@ import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; @@ -273,4 +275,20 @@ void readDetectsBlobGrowth() throws IOException { assertEquals(buffer.capacity(), buffer.position()); TestUtils.assertArraysEqual(data, halfLength, buffer.array(), 0, data.length - halfLength); } + + @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()); + } } 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 c10c4ded6d64..e86f52638947 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.crt --format PEM --no-password; + dotnet dev-certs https --export-path /mnt/outputs/dev-cert.pfx; /root/.dotnet/tools/http-fault-injector; resources: limits: @@ -30,17 +30,7 @@ spec: - | set -xa; set -o pipefail; - 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; + keytool -import -alias test -file /mnt/outputs/dev-cert.pfx -keystore ${JAVA_HOME}/lib/security/cacerts -noprompt -keypass changeit -storepass changeit; 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 fe77e9cb6f63..b558feecdcb8 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.crt --format PEM --no-password; + dotnet dev-certs https --export-path /mnt/outputs/dev-cert.pfx; /root/.dotnet/tools/http-fault-injector; resources: limits: @@ -30,17 +30,7 @@ spec: - | set -xa; set -o pipefail; - 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; + keytool -import -alias test -file /mnt/outputs/dev-cert.pfx -keystore ${JAVA_HOME}/lib/security/cacerts -noprompt -keypass changeit -storepass changeit; mkdir -p "$DEBUG_SHARE"; . /mnt/outputs/.env; export AZURE_HTTP_CLIENT_IMPLEMENTATION=com.azure.core.http.netty.NettyAsyncHttpClientProvider; From 22e19ffd27dd177ee4a187165605fc2259c34706 Mon Sep 17 00:00:00 2001 From: Isabelle Date: Mon, 1 Jun 2026 10:50:00 -0700 Subject: [PATCH 24/26] renaming files and hardcoding crc64 option --- .../scenarios-matrix.yaml | 94 +++++++------------ .../com/azure/storage/blob/stress/App.java | 10 +- ...ContentValidationDecoderStressOptions.java | 26 ----- ...ent.java => DownloadContentWithCRC64.java} | 13 +-- ...ream.java => DownloadStreamWithCRC64.java} | 13 +-- ...File.java => DownloadToFileWithCRC64.java} | 17 ++-- ...eam.java => OpenInputStreamWithCRC64.java} | 12 ++- ...OpenSeekableByteChannelReadWithCRC64.java} | 15 +-- .../templates/stress-test-job.yaml | 3 +- 9 files changed, 76 insertions(+), 127 deletions(-) delete mode 100644 sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/ContentValidationDecoderStressOptions.java rename sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/{ContentValidationDownloadContent.java => DownloadContentWithCRC64.java} (82%) rename sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/{ContentValidationDownloadStream.java => DownloadStreamWithCRC64.java} (81%) rename sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/{ContentValidationDownloadToFile.java => DownloadToFileWithCRC64.java} (86%) rename sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/{ContentValidationOpenInputStream.java => OpenInputStreamWithCRC64.java} (83%) rename sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/{ContentValidationOpenSeekableByteChannelRead.java => OpenSeekableByteChannelReadWithCRC64.java} (82%) diff --git a/sdk/storage/azure-storage-blob-stress/scenarios-matrix.yaml b/sdk/storage/azure-storage-blob-stress/scenarios-matrix.yaml index 130a81d23571..79abd617f5b6 100644 --- a/sdk/storage/azure-storage-blob-stress/scenarios-matrix.yaml +++ b/sdk/storage/azure-storage-blob-stress/scenarios-matrix.yaml @@ -109,8 +109,8 @@ matrix: imageBuildDir: "../../.." # content validation downloads using BlobDownloadStreamOptions with CRC64 validation - cvdownloadstreamsm: - testScenario: contentvalidationdownloadstream + crc64downloadstreamsm: + testScenario: downloadstreamwithcrc64 sync: true sizeBytes: 1024 downloadFaults: true @@ -118,8 +118,8 @@ matrix: imageBuildDir: "../../.." # content validation downloads using BlobDownloadStreamOptions with CRC64 validation and async client - cvdownloadstreamasyncsm: - testScenario: contentvalidationdownloadstream + crc64downloadstreamasyncsm: + testScenario: downloadstreamwithcrc64 sync: false sizeBytes: 1024 downloadFaults: true @@ -127,8 +127,8 @@ matrix: imageBuildDir: "../../.." # content validation downloads using BlobDownloadStreamOptions with CRC64 validation and large payload - cvdownloadstreamlg: - testScenario: contentvalidationdownloadstream + crc64downloadstreamlg: + testScenario: downloadstreamwithcrc64 sync: true sizeBytes: "52428800" downloadFaults: true @@ -136,27 +136,17 @@ matrix: imageBuildDir: "../../.." # content validation downloads using BlobDownloadStreamOptions with CRC64 validation, async client, and large payload - cvdownloadstreamasynclg: - testScenario: contentvalidationdownloadstream + crc64downloadstreamasynclg: + testScenario: downloadstreamwithcrc64 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 + crc64downloadcontentsm: + testScenario: downloadcontentwithcrc64 sync: true sizeBytes: 1024 downloadFaults: true @@ -164,8 +154,8 @@ matrix: imageBuildDir: "../../.." # content validation downloads using BlobDownloadContentOptions with CRC64 validation and async client - cvdownloadcontentasyncsm: - testScenario: contentvalidationdownloadcontent + crc64downloadcontentasyncsm: + testScenario: downloadcontentwithcrc64 sync: false sizeBytes: 1024 downloadFaults: true @@ -173,8 +163,8 @@ matrix: imageBuildDir: "../../.." # content validation downloads using BlobDownloadContentOptions with CRC64 validation and large payload - cvdownloadcontentlg: - testScenario: contentvalidationdownloadcontent + crc64downloadcontentlg: + testScenario: downloadcontentwithcrc64 sync: true sizeBytes: "52428800" downloadFaults: true @@ -182,27 +172,17 @@ matrix: imageBuildDir: "../../.." # content validation downloads using BlobDownloadContentOptions with CRC64 validation, async client, and large payload - cvdownloadcontentasynclg: - testScenario: contentvalidationdownloadcontent + crc64downloadcontentasynclg: + testScenario: downloadcontentwithcrc64 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 + crc64downloadfilesm: + testScenario: downloadtofilewithcrc64 sync: true sizeBytes: 1024 downloadFaults: true @@ -210,8 +190,8 @@ matrix: imageBuildDir: "../../.." # content validation downloads using BlobDownloadToFileOptions with CRC64 validation and async client - cvdownloadfileasyncsm: - testScenario: contentvalidationdownloadtofile + crc64downloadfileasyncsm: + testScenario: downloadtofilewithcrc64 sync: false sizeBytes: 1024 downloadFaults: true @@ -219,8 +199,8 @@ matrix: imageBuildDir: "../../.." # content validation downloads using BlobDownloadToFileOptions with CRC64 validation and multi-block payload - cvdownloadfilemd: - testScenario: contentvalidationdownloadtofile + crc64downloadfilemd: + testScenario: downloadtofilewithcrc64 sync: true sizeBytes: "16777216" downloadFaults: true @@ -228,27 +208,17 @@ matrix: imageBuildDir: "../../.." # content validation downloads using BlobDownloadToFileOptions with CRC64 validation, async client, and multi-block payload - cvdownloadfileasyncmd: - testScenario: contentvalidationdownloadtofile + crc64downloadfileasyncmd: + testScenario: downloadtofilewithcrc64 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 + crc64inputstreamsm: + testScenario: openinputstreamwithcrc64 sync: true sizeBytes: 1024 downloadFaults: true @@ -256,8 +226,8 @@ matrix: imageBuildDir: "../../.." # content validation downloads using BlobInputStreamOptions with CRC64 validation and large payload - cvinputstreamlg: - testScenario: contentvalidationopeninputstream + crc64inputstreamlg: + testScenario: openinputstreamwithcrc64 sync: true sizeBytes: "52428800" downloadFaults: true @@ -265,8 +235,8 @@ matrix: imageBuildDir: "../../.." # content validation downloads using BlobSeekableByteChannelReadOptions with CRC64 validation - cvbytechannelreadsm: - testScenario: contentvalidationopenseekablebytechannelread + crc64bytechannelreadsm: + testScenario: openseekablebytechannelreadwithcrc64 sync: true sizeBytes: 1024 downloadFaults: true @@ -274,8 +244,8 @@ matrix: imageBuildDir: "../../.." # content validation downloads using BlobSeekableByteChannelReadOptions with CRC64 validation and large payload - cvbytechannelreadlg: - testScenario: contentvalidationopenseekablebytechannelread + crc64bytechannelreadlg: + testScenario: openseekablebytechannelreadwithcrc64 sync: true sizeBytes: "52428800" downloadFaults: true 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 d562bbe51363..194a290d7a3c 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,11 +15,11 @@ public static void main(String[] args) { BlockBlobOutputStream.class, BlockBlobUpload.class, CommitBlockList.class, - ContentValidationDownloadContent.class, - ContentValidationDownloadStream.class, - ContentValidationDownloadToFile.class, - ContentValidationOpenInputStream.class, - ContentValidationOpenSeekableByteChannelRead.class, + DownloadContentWithCRC64.class, + DownloadStreamWithCRC64.class, + DownloadToFileWithCRC64.class, + OpenInputStreamWithCRC64.class, + OpenSeekableByteChannelReadWithCRC64.class, DownloadToFile.class, DownloadStream.class, DownloadContent.class, 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 deleted file mode 100644 index bc16742ac621..000000000000 --- a/sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/ContentValidationDecoderStressOptions.java +++ /dev/null @@ -1,26 +0,0 @@ -// 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/DownloadContentWithCRC64.java similarity index 82% rename from sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/ContentValidationDownloadContent.java rename to sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/DownloadContentWithCRC64.java index 07c549475e33..6c92e1563c60 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/DownloadContentWithCRC64.java @@ -11,20 +11,21 @@ import com.azure.storage.blob.options.BlobDownloadContentOptions; import com.azure.storage.blob.options.BlobDownloadStreamOptions; import com.azure.storage.blob.stress.utils.OriginalContent; +import com.azure.storage.common.ContentValidationAlgorithm; +import com.azure.storage.stress.StorageStressOptions; import reactor.core.publisher.Mono; /** - * Download content with - * {@link BlobDownloadContentOptions#setContentValidationAlgorithm} enabled. + * Download content with CRC64 Algorithm enabled. * Verifies the correctness of the download response content via CRC. */ -public class ContentValidationDownloadContent extends BlobScenarioBase { +public class DownloadContentWithCRC64 extends BlobScenarioBase { private final OriginalContent originalContent = new OriginalContent(); private final BlobClient syncClient; private final BlobAsyncClient asyncClient; private final BlobAsyncClient asyncNoFaultClient; - public ContentValidationDownloadContent(ContentValidationDecoderStressOptions options) { + public DownloadContentWithCRC64(StorageStressOptions options) { super(options); String blobName = generateBlobName(); this.asyncNoFaultClient = getAsyncContainerClientNoFault().getBlobAsyncClient(blobName); @@ -37,7 +38,7 @@ protected void runInternal(Context span) { originalContent.checkMatch( syncClient.downloadContentWithResponse( new BlobDownloadContentOptions() - .setContentValidationAlgorithm(options.getContentValidationAlgorithm()), + .setContentValidationAlgorithm(ContentValidationAlgorithm.CRC64), null, span).getValue(), span).block(); } @@ -47,7 +48,7 @@ protected Mono runInternalAsync(Context span) { // TODO return downloadContent once it stops buffering. return asyncClient.downloadStreamWithResponse( new BlobDownloadStreamOptions() - .setContentValidationAlgorithm(options.getContentValidationAlgorithm())) + .setContentValidationAlgorithm(ContentValidationAlgorithm.CRC64)) .flatMap(response -> { long contentLength = Long.valueOf(response.getHeaders().getValue(HttpHeaderName.CONTENT_LENGTH)); return BinaryData.fromFlux(response.getValue(), contentLength, false); 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/DownloadStreamWithCRC64.java similarity index 81% rename from sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/ContentValidationDownloadStream.java rename to sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/DownloadStreamWithCRC64.java index 1fcaf549657a..64cccb2c7dd4 100644 --- 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/DownloadStreamWithCRC64.java @@ -8,23 +8,24 @@ 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.common.ContentValidationAlgorithm; import com.azure.storage.stress.CrcOutputStream; +import com.azure.storage.stress.StorageStressOptions; import reactor.core.publisher.Mono; import java.io.IOException; /** - * Streaming blob download with - * {@link BlobDownloadStreamOptions#setContentValidationAlgorithm} enabled. + * Streaming blob download with CRC64 Algorithm enabled. * Verifies the correctness of the download response content via CRC. */ -public class ContentValidationDownloadStream extends BlobScenarioBase { +public class DownloadStreamWithCRC64 extends BlobScenarioBase { private final OriginalContent originalContent = new OriginalContent(); private final BlobClient syncClient; private final BlobAsyncClient asyncClient; private final BlobAsyncClient asyncNoFaultClient; - public ContentValidationDownloadStream(ContentValidationDecoderStressOptions options) { + public DownloadStreamWithCRC64(StorageStressOptions options) { super(options); String blobName = generateBlobName(); this.asyncNoFaultClient = getAsyncContainerClientNoFault().getBlobAsyncClient(blobName); @@ -37,7 +38,7 @@ protected void runInternal(Context span) throws IOException { try (CrcOutputStream outputStream = new CrcOutputStream()) { syncClient.downloadStreamWithResponse(outputStream, new BlobDownloadStreamOptions() - .setContentValidationAlgorithm(options.getContentValidationAlgorithm()), + .setContentValidationAlgorithm(ContentValidationAlgorithm.CRC64), null, span); outputStream.close(); originalContent.checkMatch(outputStream.getContentInfo(), span).block(); @@ -48,7 +49,7 @@ protected void runInternal(Context span) throws IOException { protected Mono runInternalAsync(Context span) { return asyncClient.downloadStreamWithResponse( new BlobDownloadStreamOptions() - .setContentValidationAlgorithm(options.getContentValidationAlgorithm())) + .setContentValidationAlgorithm(ContentValidationAlgorithm.CRC64)) .flatMap(response -> originalContent.checkMatch(response.getValue(), span)); } 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/DownloadToFileWithCRC64.java similarity index 86% rename from sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/ContentValidationDownloadToFile.java rename to sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/DownloadToFileWithCRC64.java index da52199b1339..ceb2faa8b153 100644 --- 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/DownloadToFileWithCRC64.java @@ -10,7 +10,9 @@ 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.ContentValidationAlgorithm; import com.azure.storage.common.ParallelTransferOptions; +import com.azure.storage.stress.StorageStressOptions; import reactor.core.publisher.Mono; import java.io.IOException; @@ -21,12 +23,11 @@ import java.util.UUID; /** - * Download to file with - * {@link BlobDownloadToFileOptions#setContentValidationAlgorithm} enabled. + * Download to file with CRC64 Algorithm 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); +public class DownloadToFileWithCRC64 extends BlobScenarioBase { + private static final ClientLogger LOGGER = new ClientLogger(DownloadToFileWithCRC64.class); private final Path directoryPath; private final OriginalContent originalContent = new OriginalContent(); private final BlobClient syncClient; @@ -34,7 +35,7 @@ public class ContentValidationDownloadToFile extends BlobScenarioBase runInternalAsync(Context span) { path -> asyncClient.downloadToFileWithResponse( new BlobDownloadToFileOptions(path.toString()) .setParallelTransferOptions(parallelTransferOptions) - .setContentValidationAlgorithm(options.getContentValidationAlgorithm())) + .setContentValidationAlgorithm(ContentValidationAlgorithm.CRC64)) .flatMap(ignored -> originalContent.checkMatch(BinaryData.fromFile(path), span)), - ContentValidationDownloadToFile::deleteFile); + DownloadToFileWithCRC64::deleteFile); } @Override 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/OpenInputStreamWithCRC64.java similarity index 83% rename from sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/ContentValidationOpenInputStream.java rename to sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/OpenInputStreamWithCRC64.java index 5d282c7e9c7a..eaae1570560b 100644 --- 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/OpenInputStreamWithCRC64.java @@ -9,7 +9,9 @@ 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.common.ContentValidationAlgorithm; import com.azure.storage.stress.CrcInputStream; +import com.azure.storage.stress.StorageStressOptions; import reactor.core.publisher.Mono; import java.io.IOException; @@ -18,16 +20,16 @@ import static com.azure.core.util.FluxUtil.monoError; /** - * Open input stream with {@link BlobInputStreamOptions#setContentValidationAlgorithm} enabled (sync only). + * Open input stream with CRC64 Algorithm 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); +public class OpenInputStreamWithCRC64 extends BlobScenarioBase { + private static final ClientLogger LOGGER = new ClientLogger(OpenInputStreamWithCRC64.class); private final OriginalContent originalContent = new OriginalContent(); private final BlobClient syncClient; private final BlobAsyncClient asyncNoFaultClient; - public ContentValidationOpenInputStream(ContentValidationDecoderStressOptions options) { + public OpenInputStreamWithCRC64(StorageStressOptions options) { super(options); String blobName = generateBlobName(); this.syncClient = getSyncContainerClient().getBlobClient(blobName); @@ -38,7 +40,7 @@ public ContentValidationOpenInputStream(ContentValidationDecoderStressOptions op protected void runInternal(Context span) throws IOException { try (InputStream stream = syncClient.openInputStream( new BlobInputStreamOptions() - .setContentValidationAlgorithm(options.getContentValidationAlgorithm()), + .setContentValidationAlgorithm(ContentValidationAlgorithm.CRC64), span)) { try (CrcInputStream crcStream = new CrcInputStream(stream)) { byte[] buffer = new byte[8192]; 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/OpenSeekableByteChannelReadWithCRC64.java similarity index 82% rename from sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/ContentValidationOpenSeekableByteChannelRead.java rename to sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/OpenSeekableByteChannelReadWithCRC64.java index 8de4aefd8832..e6376238fe92 100644 --- 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/OpenSeekableByteChannelReadWithCRC64.java @@ -10,7 +10,9 @@ 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.common.ContentValidationAlgorithm; import com.azure.storage.stress.CrcInputStream; +import com.azure.storage.stress.StorageStressOptions; import reactor.core.publisher.Mono; import java.io.IOException; @@ -19,18 +21,17 @@ import static com.azure.core.util.FluxUtil.monoError; /** - * Seekable byte channel read with {@link BlobSeekableByteChannelReadOptions#setContentValidationAlgorithm} - * enabled (sync only). + * Seekable byte channel read with CRC64 Algorithm 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); +public class OpenSeekableByteChannelReadWithCRC64 + extends BlobScenarioBase { + private static final ClientLogger LOGGER = new ClientLogger(OpenSeekableByteChannelReadWithCRC64.class); private final OriginalContent originalContent = new OriginalContent(); private final BlobClient syncClient; private final BlobAsyncClient asyncNoFaultClient; - public ContentValidationOpenSeekableByteChannelRead(ContentValidationDecoderStressOptions options) { + public OpenSeekableByteChannelReadWithCRC64(StorageStressOptions options) { super(options); String blobName = generateBlobName(); this.asyncNoFaultClient = getAsyncContainerClientNoFault().getBlobAsyncClient(blobName); @@ -41,7 +42,7 @@ public ContentValidationOpenSeekableByteChannelRead(ContentValidationDecoderStre protected void runInternal(Context span) throws IOException { BlobSeekableByteChannelReadResult result = syncClient.openSeekableByteChannelRead( new BlobSeekableByteChannelReadOptions() - .setContentValidationAlgorithm(options.getContentValidationAlgorithm()), + .setContentValidationAlgorithm(ContentValidationAlgorithm.CRC64), span); try (CrcInputStream crcStream = new CrcInputStream(Channels.newInputStream(result.getChannel()))) { byte[] buffer = new byte[8192]; 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 e1a73df2c7e7..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 @@ -63,7 +63,6 @@ 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=$?; @@ -78,4 +77,4 @@ spec: cpu: "0.7" {{- include "stress-test-addons.container-env" . | nindent 6 }} -{{- end -}} \ No newline at end of file +{{- end -}} From 68d5be0c14b0ae982dceff3f8fb039daa59f300e Mon Sep 17 00:00:00 2001 From: Isabelle Date: Mon, 1 Jun 2026 13:16:44 -0700 Subject: [PATCH 25/26] removing unused test --- ...SeekableByteChannelBlobReadBehaviorTests.java | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/StorageSeekableByteChannelBlobReadBehaviorTests.java b/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/StorageSeekableByteChannelBlobReadBehaviorTests.java index d0333c057ef8..52b1273aaa9c 100644 --- a/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/StorageSeekableByteChannelBlobReadBehaviorTests.java +++ b/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/StorageSeekableByteChannelBlobReadBehaviorTests.java @@ -275,20 +275,4 @@ void readDetectsBlobGrowth() throws IOException { assertEquals(buffer.capacity(), buffer.position()); TestUtils.assertArraysEqual(data, halfLength, buffer.array(), 0, data.length - halfLength); } - - @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()); - } } From 44865dfcc0cc28f5299ba5fa607c6f611ecd009e Mon Sep 17 00:00:00 2001 From: Isabelle Date: Mon, 1 Jun 2026 13:17:29 -0700 Subject: [PATCH 26/26] removing unused imports --- .../StorageSeekableByteChannelBlobReadBehaviorTests.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/StorageSeekableByteChannelBlobReadBehaviorTests.java b/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/StorageSeekableByteChannelBlobReadBehaviorTests.java index 52b1273aaa9c..5ccc9dfc1ea4 100644 --- a/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/StorageSeekableByteChannelBlobReadBehaviorTests.java +++ b/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/StorageSeekableByteChannelBlobReadBehaviorTests.java @@ -12,7 +12,6 @@ import com.azure.storage.blob.models.BlobDownloadResponse; import com.azure.storage.blob.models.BlobRange; import com.azure.storage.blob.models.BlobRequestConditions; -import com.azure.storage.blob.models.DownloadRetryOptions; import com.azure.storage.blob.models.PageRange; import com.azure.storage.common.implementation.Constants; import org.junit.jupiter.api.AfterEach; @@ -36,7 +35,6 @@ import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq;