Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1369,6 +1369,9 @@ class EncyptedBlockBlobAPITest extends APISpec {
@Unroll
def "Encryption uploadIS numBlocks"() {
setup:
if (numBlocks > 0 && !liveMode()) {
return // Multipart upload paths use randomly generated block ids that can't be recorded and played back.
}
def randomData = getRandomByteArray(size)
def input = new ByteArrayInputStream(randomData)

Expand Down

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions sdk/storage/azure-storage-blob/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Release History

## 12.7.0-beta.1 (Unreleased)
- Added an overload to BlobClient.upload which returns a BlockBlobItem containing the properties returned by the service upon blob creation.
- Fixed a bug that caused auth failures when constructing a client to a secondary endpoint using token auth.

## 12.6.1 (2020-05-06)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,6 @@ private Mono<Response<BlockBlobItem>> uploadInChunks(BlockBlobAsyncClient blockB

final String blockId = Base64.getEncoder().encodeToString(
UUID.randomUUID().toString().getBytes(UTF_8));

return blockBlobAsyncClient.stageBlockWithResponse(blockId, progressData, buffer.remaining(),
null, requestConditions.getLeaseId())
// We only care about the stageBlock insofar as it was successful,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,28 @@
package com.azure.storage.blob;

import com.azure.core.annotation.ServiceClient;
import com.azure.core.http.rest.Response;
import com.azure.core.util.Context;
import com.azure.core.util.FluxUtil;
import com.azure.core.util.logging.ClientLogger;
import com.azure.storage.blob.implementation.util.ModelHelper;
import com.azure.storage.blob.models.AccessTier;
import com.azure.storage.blob.models.BlobParallelUploadOptions;
import com.azure.storage.blob.models.BlobRequestConditions;
import com.azure.storage.blob.models.BlobHttpHeaders;
import com.azure.storage.blob.models.BlobStorageException;
import com.azure.storage.blob.models.BlockBlobItem;
import com.azure.storage.blob.models.ParallelTransferOptions;
import com.azure.storage.blob.specialized.AppendBlobClient;
import com.azure.storage.blob.specialized.BlobClientBase;
import com.azure.storage.blob.specialized.BlobOutputStream;
import com.azure.storage.blob.specialized.BlockBlobClient;
import com.azure.storage.blob.specialized.PageBlobClient;
import com.azure.storage.blob.specialized.SpecializedBlobClientBuilder;
import com.azure.storage.common.Utility;
import com.azure.storage.common.implementation.Constants;
import com.azure.storage.common.implementation.StorageImplUtils;
import com.azure.storage.common.implementation.UploadUtils;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.time.Duration;
Expand Down Expand Up @@ -169,29 +170,39 @@ public void upload(InputStream data, long length, boolean overwrite) {
public void uploadWithResponse(InputStream data, long length, ParallelTransferOptions parallelTransferOptions,
BlobHttpHeaders headers, Map<String, String> metadata, AccessTier tier, BlobRequestConditions requestConditions,
Duration timeout, Context context) {
uploadWithResponse(data, length, new BlobParallelUploadOptions()
.setParallelTransferOptions(parallelTransferOptions).setHeaders(headers).setMetadata(metadata).setTier(tier)
.setRequestConditions(requestConditions), timeout, context);
}

/**
* Creates a new blob, or updates the content of an existing blob.
* <p>
* To avoid overwriting, pass "*" to {@link BlobRequestConditions#setIfNoneMatch(String)}.
*
* @param data The data to write to the blob.
* @param length The exact length of the data. It is important that this value match precisely the length of the
* data provided in the {@link InputStream}.
* @param options {@link BlobParallelUploadOptions}
* @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised.
* @param context Additional context that is passed through the Http pipeline during the service call.
* @return Information about the uploaded block blob.
*/
public Response<BlockBlobItem> uploadWithResponse(InputStream data, long length, BlobParallelUploadOptions options,
Duration timeout, Context context) {
BlobParallelUploadOptions blobParallelUploadOptions = options == null ? new BlobParallelUploadOptions()
: options;
final ParallelTransferOptions validatedParallelTransferOptions =
ModelHelper.populateAndApplyDefaults(parallelTransferOptions);
Mono<Object> upload = Mono.fromCallable(() -> {
try {
// BlobOutputStream will internally handle the decision for single-shot or multi-part upload.
BlobOutputStream blobOutputStream = BlobOutputStream.blockBlobOutputStream(client,
validatedParallelTransferOptions, headers, metadata, tier, requestConditions, context);
StorageImplUtils.copyToOutputStream(data, length, blobOutputStream);
blobOutputStream.close();
return null;
} catch (IOException e) {
Throwable cause = e.getCause();
if (cause instanceof BlobStorageException) {
throw logger.logExceptionAsError((BlobStorageException) cause);
} else {
throw logger.logExceptionAsError(new UncheckedIOException(e));
}
}
// Subscribing has to happen on a different thread for the timeout to happen properly.
}).subscribeOn(Schedulers.elastic());
ModelHelper.populateAndApplyDefaults(blobParallelUploadOptions.getParallelTransferOptions());

Mono<Response<BlockBlobItem>> upload = client.uploadWithResponse(Utility.convertStreamToByteBuffer(data, length,
validatedParallelTransferOptions.getBlockSize()), validatedParallelTransferOptions,
blobParallelUploadOptions.getHeaders(), blobParallelUploadOptions.getMetadata(),
blobParallelUploadOptions.getTier(), blobParallelUploadOptions.getRequestConditions())
.subscriberContext(FluxUtil.toReactorContext(context));

try {
StorageImplUtils.blockWithOptionalTimeout(upload, timeout);
return StorageImplUtils.blockWithOptionalTimeout(upload, timeout);
} catch (UncheckedIOException e) {
throw logger.logExceptionAsError(e);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.storage.blob.models;

import com.azure.core.annotation.Fluent;

import java.util.Map;

/**
* Extended options that may be passed when uploading a Block Blob in parallel.
*/
@Fluent
public class BlobParallelUploadOptions {
private ParallelTransferOptions parallelTransferOptions;
private BlobHttpHeaders headers;
private Map<String, String> metadata;
private AccessTier tier;
private BlobRequestConditions requestConditions;

/**
* @return {@link ParallelTransferOptions}
*/
public ParallelTransferOptions getParallelTransferOptions() {
return parallelTransferOptions;
}

/**
* @param parallelTransferOptions {@link ParallelTransferOptions}
* @return The updated options.
*/
public BlobParallelUploadOptions setParallelTransferOptions(ParallelTransferOptions parallelTransferOptions) {
this.parallelTransferOptions = parallelTransferOptions;
return this;
}

/**
* @return {@link BlobHttpHeaders}
*/
public BlobHttpHeaders getHeaders() {
return headers;
}

/**
* @param headers {@link BlobHttpHeaders}
* @return The updated options
*/
public BlobParallelUploadOptions setHeaders(BlobHttpHeaders headers) {
this.headers = headers;
return this;
}

/**
* @return The metadata to associate with the blob.
*/
public Map<String, String> getMetadata() {
return metadata;
}

/**
* @param metadata The metadata to associate with the blob.
* @return The updated options.
*/
public BlobParallelUploadOptions setMetadata(Map<String, String> metadata) {
this.metadata = metadata;
return this;
}

/**
* @return {@link AccessTier}
*/
public AccessTier getTier() {
return tier;
}

/**
* @param tier {@link AccessTier}
* @return The updated options.
*/
public BlobParallelUploadOptions setTier(AccessTier tier) {
this.tier = tier;
return this;
}

/**
* @return {@link BlobRequestConditions}
*/
public BlobRequestConditions getRequestConditions() {
return requestConditions;
}

/**
* @param requestConditions {@link BlobRequestConditions}
* @return The updated options.
*/
public BlobParallelUploadOptions setRequestConditions(BlobRequestConditions requestConditions) {
this.requestConditions = requestConditions;
return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,6 @@ class APISpec extends Specification {
interceptorManager.close()
}

//TODO: Should this go in core.
static Mono<ByteBuffer> collectBytesInBuffer(Flux<ByteBuffer> content) {
return FluxUtil.collectBytesInByteBufferStream(content).map { bytes -> ByteBuffer.wrap(bytes) }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ class BlobAPITest extends APISpec {
@Unroll
def "Upload numBlocks"() {
setup:
if (numBlocks > 0 && !liveMode()) {
return // skip multipart upload for playback/record as it uses randomly generated block ids
}
def randomData = getRandomByteArray(size)
def input = new ByteArrayInputStream(randomData)

Expand All @@ -121,6 +124,11 @@ class BlobAPITest extends APISpec {
3 * Constants.MB| Constants.MB || 3
}

def "Upload return value"() {
expect:
bc.uploadWithResponse(defaultInputStream.get(), defaultDataSize, null, null, null).getValue().getETag() != null
}

@Requires({ liveMode() }) // Reading from recordings will not allow for the timing of the test to work correctly.
def "Upload timeout"() {
setup:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
{
"networkCallRecords" : [ {
"Method" : "PUT",
"Uri" : "https://REDACTED.blob.core.windows.net/jtcuploadreturnvalue0blobapitestuploadreturnvalueac375005572?restype=container",
"Headers" : {
"x-ms-version" : "2019-07-07",
"User-Agent" : "azsdk-java-azure-storage-blob/12.7.0-beta.1 (11.0.5; Windows 10 10.0)",
"x-ms-client-request-id" : "18215bde-49e1-4684-98b4-1ffc35ae1fe5"
},
"Response" : {
"x-ms-version" : "2019-07-07",
"Server" : "Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0",
"ETag" : "0x8D801A7397A7BE3",
"Last-Modified" : "Tue, 26 May 2020 19:01:40 GMT",
"retry-after" : "0",
"Content-Length" : "0",
"StatusCode" : "201",
"x-ms-request-id" : "dc92cad7-e01e-0130-2090-330d62000000",
"Date" : "Tue, 26 May 2020 19:01:40 GMT",
"x-ms-client-request-id" : "18215bde-49e1-4684-98b4-1ffc35ae1fe5"
},
"Exception" : null
}, {
"Method" : "PUT",
"Uri" : "https://REDACTED.blob.core.windows.net/jtcuploadreturnvalue0blobapitestuploadreturnvalueac375005572/javablobuploadreturnvalue1blobapitestuploadreturnvalueac350727",
"Headers" : {
"x-ms-version" : "2019-07-07",
"User-Agent" : "azsdk-java-azure-storage-blob/12.7.0-beta.1 (11.0.5; Windows 10 10.0)",
"x-ms-client-request-id" : "c32a1b3d-0aaf-4bc7-815a-eb26ea2e2d9c",
"Content-Type" : "application/octet-stream"
},
"Response" : {
"x-ms-version" : "2019-07-07",
"Server" : "Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0",
"x-ms-content-crc64" : "6RYQPwaVsyQ=",
"Last-Modified" : "Tue, 26 May 2020 19:01:41 GMT",
"retry-after" : "0",
"StatusCode" : "201",
"x-ms-request-server-encrypted" : "true",
"Date" : "Tue, 26 May 2020 19:01:41 GMT",
"Content-MD5" : "wh+Wm18D0z1D4E+PE252gg==",
"ETag" : "0x8D801A739B591A0",
"Content-Length" : "0",
"x-ms-request-id" : "35a22e23-e01e-0016-6c90-33d083000000",
"x-ms-client-request-id" : "c32a1b3d-0aaf-4bc7-815a-eb26ea2e2d9c"
},
"Exception" : null
}, {
"Method" : "PUT",
"Uri" : "https://REDACTED.blob.core.windows.net/jtcuploadreturnvalue0blobapitestuploadreturnvalueac375005572/javablobuploadreturnvalue1blobapitestuploadreturnvalueac350727",
"Headers" : {
"x-ms-version" : "2019-07-07",
"User-Agent" : "azsdk-java-azure-storage-blob/12.7.0-beta.1 (11.0.5; Windows 10 10.0)",
"x-ms-client-request-id" : "97217080-b607-4c5a-9377-9402de2fbbb0",
"Content-Type" : "application/octet-stream"
},
"Response" : {
"x-ms-version" : "2019-07-07",
"Server" : "Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0",
"x-ms-content-crc64" : "6RYQPwaVsyQ=",
"Last-Modified" : "Tue, 26 May 2020 19:01:41 GMT",
"retry-after" : "0",
"StatusCode" : "201",
"x-ms-request-server-encrypted" : "true",
"Date" : "Tue, 26 May 2020 19:01:40 GMT",
"Content-MD5" : "wh+Wm18D0z1D4E+PE252gg==",
"ETag" : "0x8D801A739DBE7A7",
"Content-Length" : "0",
"x-ms-request-id" : "45a43c8b-501e-0129-5090-33210a000000",
"x-ms-client-request-id" : "97217080-b607-4c5a-9377-9402de2fbbb0"
},
"Exception" : null
}, {
"Method" : "GET",
"Uri" : "https://REDACTED.blob.core.windows.net?prefix=jtcuploadreturnvalue&comp=list",
"Headers" : {
"x-ms-version" : "2019-07-07",
"User-Agent" : "azsdk-java-azure-storage-blob/12.7.0-beta.1 (11.0.5; Windows 10 10.0)",
"x-ms-client-request-id" : "77ce1921-fe00-4f0d-89e6-81b20026fb4a"
},
"Response" : {
"Transfer-Encoding" : "chunked",
"x-ms-version" : "2019-07-07",
"Server" : "Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0",
"retry-after" : "0",
"StatusCode" : "200",
"x-ms-request-id" : "953dace8-b01e-0128-5690-3320f7000000",
"Body" : "<?xml version=\"1.0\" encoding=\"utf-8\"?><EnumerationResults ServiceEndpoint=\"https://xclientdev2.blob.core.windows.net/\"><Prefix>jtcuploadreturnvalue</Prefix><Containers><Container><Name>jtcuploadreturnvalue0blobapitestuploadreturnvalueac375005572</Name><Properties><Last-Modified>Tue, 26 May 2020 19:01:40 GMT</Last-Modified><Etag>\"0x8D801A7397A7BE3\"</Etag><LeaseStatus>unlocked</LeaseStatus><LeaseState>available</LeaseState><DefaultEncryptionScope>$account-encryption-key</DefaultEncryptionScope><DenyEncryptionScopeOverride>false</DenyEncryptionScopeOverride><HasImmutabilityPolicy>false</HasImmutabilityPolicy><HasLegalHold>false</HasLegalHold></Properties></Container></Containers><NextMarker /></EnumerationResults>",
"Date" : "Tue, 26 May 2020 19:01:41 GMT",
"x-ms-client-request-id" : "77ce1921-fe00-4f0d-89e6-81b20026fb4a",
"Content-Type" : "application/xml"
},
"Exception" : null
}, {
"Method" : "DELETE",
"Uri" : "https://REDACTED.blob.core.windows.net/jtcuploadreturnvalue0blobapitestuploadreturnvalueac375005572?restype=container",
"Headers" : {
"x-ms-version" : "2019-07-07",
"User-Agent" : "azsdk-java-azure-storage-blob/12.7.0-beta.1 (11.0.5; Windows 10 10.0)",
"x-ms-client-request-id" : "7bd95de9-39e6-43c6-9798-8e034a96a037"
},
"Response" : {
"x-ms-version" : "2019-07-07",
"Server" : "Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0",
"retry-after" : "0",
"Content-Length" : "0",
"StatusCode" : "202",
"x-ms-request-id" : "ef6e1ddd-001e-001c-4a90-33c90a000000",
"Date" : "Tue, 26 May 2020 19:01:41 GMT",
"x-ms-client-request-id" : "7bd95de9-39e6-43c6-9798-8e034a96a037"
},
"Exception" : null
} ],
"variables" : [ "jtcuploadreturnvalue0blobapitestuploadreturnvalueac375005572", "javablobuploadreturnvalue1blobapitestuploadreturnvalueac350727" ]
}