From 4394b79bc0a7043f74b3187278a23982ee364404 Mon Sep 17 00:00:00 2001 From: browndav Date: Mon, 17 Nov 2025 16:42:48 -0500 Subject: [PATCH] create blobmetadataoperations test and add to app for stress testing --- .../com/azure/storage/blob/stress/App.java | 1 + .../blob/stress/BlobMetadataOperations.java | 145 ++++++++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/BlobMetadataOperations.java 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..780a45193b9f 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,7 @@ public static void main(String[] args) { BlockBlobOutputStream.class, BlockBlobUpload.class, CommitBlockList.class, + BlobMetadataOperations.class, DownloadToFile.class, DownloadStream.class, DownloadContent.class, diff --git a/sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/BlobMetadataOperations.java b/sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/BlobMetadataOperations.java new file mode 100644 index 000000000000..23a077465742 --- /dev/null +++ b/sdk/storage/azure-storage-blob-stress/src/main/java/com/azure/storage/blob/stress/BlobMetadataOperations.java @@ -0,0 +1,145 @@ +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.BlobContainerClient; +import com.azure.storage.blob.models.BlobProperties; +import com.azure.storage.blob.models.ParallelTransferOptions; +import com.azure.storage.blob.stress.utils.OriginalContent; +import com.azure.storage.stress.StorageStressOptions; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.security.SecureRandom; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * Stress test for blob metadata operations including setting, getting, and updating metadata. + * This test validates the performance and reliability of metadata operations under high load. + */ +public class BlobMetadataOperations extends BlobScenarioBase { + private static final ClientLogger LOGGER = new ClientLogger(BlockBlobOutputStream.class); + private final OriginalContent originalContent = new OriginalContent(); + private final BlobClient syncClient; + private final BlobAsyncClient asyncNoFaultClient; + private final ParallelTransferOptions parallelTransferOptions; + + private static final String BLOB_NAME_PREFIX = "metadata-test-blob-"; + private static final int METADATA_PAIRS_COUNT = 10; + + public BlobMetadataOperations(StorageStressOptions options) { + super(options); + String blobName = generateBlobName(); + this.asyncNoFaultClient = getAsyncContainerClientNoFault().getBlobAsyncClient(blobName); + this.syncClient = getSyncContainerClientNoFault().getBlobClient(blobName); + this.parallelTransferOptions = new ParallelTransferOptions().setMaxConcurrency(options.getMaxConcurrency()); + } + + @Override + protected void runInternal(Context span) { + + Map metadata = generateRandomMetadata(); + syncClient.setMetadata(metadata); + BlobProperties properties = syncClient.getProperties(); + Map retrievedMetadata = properties.getMetadata(); + if (retrievedMetadata == null || retrievedMetadata.size() != metadata.size()) { + throw new RuntimeException("Metadata validation failed: expected " + metadata.size() + + " pairs but got " + (retrievedMetadata != null ? retrievedMetadata.size() : 0)); + } + Map updatedMetadata = generateRandomMetadata(); + syncClient.setMetadata(updatedMetadata); + syncClient.setMetadata(new HashMap<>()); + } + + @Override + protected Mono runInternalAsync(Context span) { + return ensureBlobExistsAsync() + .then(setInitialMetadataAsync()) + .flatMap(this::validateMetadataAsync) + .then(updateMetadataAsync()) + .then(clearMetadataAsync()); + } + + + @Override + public Mono setupAsync() { + return super.setupAsync().then(originalContent.setupBlob(asyncNoFaultClient, options.getSize())); + } + + @Override + public Mono cleanupAsync() { + return asyncNoFaultClient.delete() + .then(super.cleanupAsync()); + } + + private Map generateRandomMetadata() { + Map metadata = new HashMap<>(); + for (int i = 0; i < METADATA_PAIRS_COUNT; i++) { + String key = "key" + i + "_" + System.currentTimeMillis(); + String value = "value_" + UUID.randomUUID().toString().substring(0, 8); + metadata.put(key, value); + } + return metadata; + } + + private Flux generateRandomByteBuffer(int size) { + byte[] data = new byte[size]; + new SecureRandom().nextBytes(data); + return Flux.just(ByteBuffer.wrap(data)); + } + + + private InputStream generateRandomBinaryData(int size) { + byte[] data = new byte[size]; + new SecureRandom().nextBytes(data); + return new ByteArrayInputStream(data); + } + + + private Mono ensureBlobExistsAsync() { + return asyncNoFaultClient.exists() + .flatMap(exists -> { + if (exists) { + return Mono.empty(); + } + return asyncNoFaultClient.upload(generateRandomByteBuffer(1024), parallelTransferOptions, true) + .then(); + }); + } + + private Mono> setInitialMetadataAsync() { + Map metadata = generateRandomMetadata(); + return asyncNoFaultClient.setMetadata(metadata) + .thenReturn(metadata); + } + + private Mono validateMetadataAsync(Map expectedMetadata) { + return asyncNoFaultClient.getProperties() + .flatMap(properties -> { + Map retrievedMetadata = properties.getMetadata(); + if (retrievedMetadata == null || retrievedMetadata.size() != expectedMetadata.size()) { + return Mono.error(new RuntimeException("Async metadata validation failed")); + } + return Mono.empty(); + }); + } + + private Mono updateMetadataAsync() { + Map updatedMetadata = generateRandomMetadata(); + return asyncNoFaultClient.setMetadata(updatedMetadata) + .then(); + } + + private Mono clearMetadataAsync() { + return asyncNoFaultClient.setMetadata(new HashMap<>()) + .then(); + } + +}