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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 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,20 +4,25 @@
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;
Expand Down Expand Up @@ -169,29 +174,38 @@ 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.
*/
public Response<BlockBlobItem> uploadWithResponse(InputStream data, long length, BlobParallelUploadOptions options,
Comment thread
alzimmermsft marked this conversation as resolved.
Comment thread
alzimmermsft marked this conversation as resolved.
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());
Comment thread
gapra-msft marked this conversation as resolved.

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 {
Comment thread
alzimmermsft marked this conversation as resolved.
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" ]
}