New API: CopyFromUri, which copies a blob synchronously (#3098)
* sync copy blob * CL * Update sdk/storage/azure-storage-blobs/CHANGELOG.md Co-authored-by: Ahson Khan <ahkha@microsoft.com> * Update sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/blob_client.hpp Co-authored-by: Ahson Khan <ahkha@microsoft.com> * Update sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/blob_client.hpp Co-authored-by: Ahson Khan <ahkha@microsoft.com> Co-authored-by: Ahson Khan <ahkha@microsoft.com>
This commit is contained in:
parent
1f07a30133
commit
f9157c8763
@ -1,5 +1,17 @@
|
||||
# Release History
|
||||
|
||||
## 12.3.0 (Unreleased)
|
||||
|
||||
### Features Added
|
||||
|
||||
- New API: `BlobClient::CopyFromUri()`.
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
### Bugs Fixed
|
||||
|
||||
### Other Changes
|
||||
|
||||
## 12.2.1 (2021-11-08)
|
||||
|
||||
### Other Changes
|
||||
|
||||
@ -202,6 +202,23 @@ namespace Azure { namespace Storage { namespace Blobs {
|
||||
const SetBlobAccessTierOptions& options = SetBlobAccessTierOptions(),
|
||||
const Azure::Core::Context& context = Azure::Core::Context()) const;
|
||||
|
||||
/**
|
||||
* @brief Copies data from the source to this blob, synchronously.
|
||||
*
|
||||
* @param sourceUri Specifies the URL of the source blob. The value may be a URL of up to 2 KB
|
||||
* in length that specifies a blob. The value should be URL-encoded as it would appear in a
|
||||
* request URI. The source blob must either be public or must be authorized via a shared access
|
||||
* signature. If the size of the source blob is greater than 256 MB, the request will fail with
|
||||
* 409 (Conflict). The blob type of the source blob has to be block blob.
|
||||
* @param options Optional parameters to execute this function.
|
||||
* @param context Context for cancelling long running operations.
|
||||
* @return A CopyBlobFromUriResult describing the copy result.
|
||||
*/
|
||||
Azure::Response<Models::CopyBlobFromUriResult> CopyFromUri(
|
||||
const std::string& sourceUri,
|
||||
const CopyBlobFromUriOptions& options = CopyBlobFromUriOptions(),
|
||||
const Azure::Core::Context& context = Azure::Core::Context()) const;
|
||||
|
||||
/**
|
||||
* @brief Copies data at from the source to this blob.
|
||||
*
|
||||
|
||||
@ -497,6 +497,52 @@ namespace Azure { namespace Storage { namespace Blobs {
|
||||
Azure::Nullable<bool> ShouldSealDestination;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Optional parameters for #Azure::Storage::Blobs::BlobClient::CopyFromUri.
|
||||
*/
|
||||
struct CopyBlobFromUriOptions
|
||||
{
|
||||
/**
|
||||
* @brief Specifies user-defined name-value pairs associated with the blob. If no
|
||||
* name-value pairs are specified, the operation will copy the metadata from the source blob or
|
||||
* file to the destination blob. If one or more name-value pairs are specified, the destination
|
||||
* blob is created with the specified metadata, and metadata is not copied from the source blob
|
||||
* or file.
|
||||
*/
|
||||
Storage::Metadata Metadata;
|
||||
|
||||
/**
|
||||
* @brief The tags to set for this blob.
|
||||
*/
|
||||
std::map<std::string, std::string> Tags;
|
||||
|
||||
/**
|
||||
* @brief Optional conditions that must be met to perform this operation.
|
||||
*/
|
||||
BlobAccessConditions AccessConditions;
|
||||
|
||||
/**
|
||||
* @brief Optional conditions that the source must meet to perform this operation.
|
||||
*
|
||||
* @note Lease access condition only works for API versions before 2012-02-12.
|
||||
*/
|
||||
struct : public Azure::ModifiedConditions, public Azure::MatchConditions
|
||||
{
|
||||
} SourceAccessConditions;
|
||||
|
||||
/**
|
||||
* @brief Specifies the tier to be set on the target blob.
|
||||
*/
|
||||
Azure::Nullable<Models::AccessTier> AccessTier;
|
||||
|
||||
/**
|
||||
* @brief Hash of the blob content. This hash is used to verify the integrity of
|
||||
* the blob during transport. When this header is specified, the storage service checks the hash
|
||||
* that has arrived with the one that was sent.
|
||||
*/
|
||||
Azure::Nullable<ContentHash> TransactionalContentHash;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Optional parameters for #Azure::Storage::Blobs::BlobClient::AbortCopyFromUri.
|
||||
*/
|
||||
|
||||
@ -1635,6 +1635,41 @@ namespace Azure { namespace Storage { namespace Blobs {
|
||||
Azure::Nullable<ContentHash> TransactionalContentHash;
|
||||
}; // struct CommitBlockListResult
|
||||
|
||||
/**
|
||||
* @brief Response type for #Azure::Storage::Blobs::BlobClient::CopyFromUri.
|
||||
*/
|
||||
struct CopyBlobFromUriResult final
|
||||
{
|
||||
/**
|
||||
* The ETag contains a value that you can use to perform operations conditionally.
|
||||
*/
|
||||
Azure::ETag ETag;
|
||||
/**
|
||||
* The date and time the container was last modified. Any operation that modifies the blob,
|
||||
* including an update of the metadata or properties, changes the last-modified time of the
|
||||
* blob.
|
||||
*/
|
||||
Azure::DateTime LastModified;
|
||||
/**
|
||||
* String identifier for the last attempted Copy Blob operation where this blob was the
|
||||
* destination. This value is null if this blob has never been the destination of a copy
|
||||
* operation, or if this blob has been modified after a concluded copy operation.
|
||||
*/
|
||||
std::string CopyId;
|
||||
/**
|
||||
* State of the copy operation identified by the copy ID. Possible values include success,
|
||||
* pending, aborted, failed etc. This value is null if this blob has never been the
|
||||
* destination of a copy operation, or if this blob has been modified after a concluded copy
|
||||
* operation.
|
||||
*/
|
||||
Models::CopyStatus CopyStatus;
|
||||
/**
|
||||
* A string value that uniquely identifies the blob. This value is null if Blob Versioning is
|
||||
* not enabled.
|
||||
*/
|
||||
Azure::Nullable<std::string> VersionId;
|
||||
}; // struct CopyBlobFromUriResult
|
||||
|
||||
/**
|
||||
* @brief Response type for #Azure::Storage::Blobs::AppendBlobClient::Create.
|
||||
*/
|
||||
@ -7696,6 +7731,153 @@ namespace Azure { namespace Storage { namespace Blobs {
|
||||
return SetAccessTierCreateResponse(std::move(pHttpResponse), context);
|
||||
}
|
||||
|
||||
struct CopyBlobFromUriOptions final
|
||||
{
|
||||
Azure::Nullable<int32_t> Timeout;
|
||||
Storage::Metadata Metadata;
|
||||
std::map<std::string, std::string> Tags;
|
||||
std::string SourceUri;
|
||||
Azure::Nullable<std::string> LeaseId;
|
||||
Azure::Nullable<Models::AccessTier> AccessTier;
|
||||
Azure::Nullable<Azure::DateTime> IfModifiedSince;
|
||||
Azure::Nullable<Azure::DateTime> IfUnmodifiedSince;
|
||||
Azure::ETag IfMatch;
|
||||
Azure::ETag IfNoneMatch;
|
||||
Azure::Nullable<std::string> IfTags;
|
||||
Azure::Nullable<Azure::DateTime> SourceIfModifiedSince;
|
||||
Azure::Nullable<Azure::DateTime> SourceIfUnmodifiedSince;
|
||||
Azure::ETag SourceIfMatch;
|
||||
Azure::ETag SourceIfNoneMatch;
|
||||
Azure::Nullable<ContentHash> TransactionalContentHash;
|
||||
}; // struct CopyBlobFromUriOptions
|
||||
|
||||
static Azure::Response<CopyBlobFromUriResult> CopyFromUri(
|
||||
Azure::Core::Http::_internal::HttpPipeline& pipeline,
|
||||
const Azure::Core::Url& url,
|
||||
const CopyBlobFromUriOptions& options,
|
||||
const Azure::Core::Context& context)
|
||||
{
|
||||
auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url);
|
||||
request.SetHeader("Content-Length", "0");
|
||||
request.SetHeader("x-ms-requires-sync", "true");
|
||||
request.SetHeader("x-ms-version", "2020-02-10");
|
||||
if (options.Timeout.HasValue())
|
||||
{
|
||||
request.GetUrl().AppendQueryParameter(
|
||||
"timeout", std::to_string(options.Timeout.Value()));
|
||||
}
|
||||
for (const auto& pair : options.Metadata)
|
||||
{
|
||||
request.SetHeader("x-ms-meta-" + pair.first, pair.second);
|
||||
}
|
||||
if (!options.Tags.empty())
|
||||
{
|
||||
std::string blobTagsValue;
|
||||
for (const auto& tag : options.Tags)
|
||||
{
|
||||
if (!blobTagsValue.empty())
|
||||
{
|
||||
blobTagsValue += "&";
|
||||
}
|
||||
blobTagsValue += _internal::UrlEncodeQueryParameter(tag.first) + "="
|
||||
+ _internal::UrlEncodeQueryParameter(tag.second);
|
||||
}
|
||||
request.SetHeader("x-ms-tags", std::move(blobTagsValue));
|
||||
}
|
||||
request.SetHeader("x-ms-copy-source", options.SourceUri);
|
||||
if (options.LeaseId.HasValue())
|
||||
{
|
||||
request.SetHeader("x-ms-lease-id", options.LeaseId.Value());
|
||||
}
|
||||
if (options.AccessTier.HasValue())
|
||||
{
|
||||
request.SetHeader("x-ms-access-tier", options.AccessTier.Value().ToString());
|
||||
}
|
||||
if (options.IfModifiedSince.HasValue())
|
||||
{
|
||||
request.SetHeader(
|
||||
"If-Modified-Since",
|
||||
options.IfModifiedSince.Value().ToString(Azure::DateTime::DateFormat::Rfc1123));
|
||||
}
|
||||
if (options.IfUnmodifiedSince.HasValue())
|
||||
{
|
||||
request.SetHeader(
|
||||
"If-Unmodified-Since",
|
||||
options.IfUnmodifiedSince.Value().ToString(Azure::DateTime::DateFormat::Rfc1123));
|
||||
}
|
||||
if (options.IfMatch.HasValue() && !options.IfMatch.ToString().empty())
|
||||
{
|
||||
request.SetHeader("If-Match", options.IfMatch.ToString());
|
||||
}
|
||||
if (options.IfNoneMatch.HasValue() && !options.IfNoneMatch.ToString().empty())
|
||||
{
|
||||
request.SetHeader("If-None-Match", options.IfNoneMatch.ToString());
|
||||
}
|
||||
if (options.IfTags.HasValue())
|
||||
{
|
||||
request.SetHeader("x-ms-if-tags", options.IfTags.Value());
|
||||
}
|
||||
if (options.SourceIfModifiedSince.HasValue())
|
||||
{
|
||||
request.SetHeader(
|
||||
"x-ms-source-if-modified-since",
|
||||
options.SourceIfModifiedSince.Value().ToString(
|
||||
Azure::DateTime::DateFormat::Rfc1123));
|
||||
}
|
||||
if (options.SourceIfUnmodifiedSince.HasValue())
|
||||
{
|
||||
request.SetHeader(
|
||||
"x-ms-source-if-unmodified-since",
|
||||
options.SourceIfUnmodifiedSince.Value().ToString(
|
||||
Azure::DateTime::DateFormat::Rfc1123));
|
||||
}
|
||||
if (options.SourceIfMatch.HasValue() && !options.SourceIfMatch.ToString().empty())
|
||||
{
|
||||
request.SetHeader("x-ms-source-if-match", options.SourceIfMatch.ToString());
|
||||
}
|
||||
if (options.SourceIfNoneMatch.HasValue() && !options.SourceIfNoneMatch.ToString().empty())
|
||||
{
|
||||
request.SetHeader("x-ms-source-if-none-match", options.SourceIfNoneMatch.ToString());
|
||||
}
|
||||
if (options.TransactionalContentHash.HasValue())
|
||||
{
|
||||
if (options.TransactionalContentHash.Value().Algorithm == HashAlgorithm::Md5)
|
||||
{
|
||||
request.SetHeader(
|
||||
"x-ms-source-content-md5",
|
||||
Azure::Core::Convert::Base64Encode(
|
||||
options.TransactionalContentHash.Value().Value));
|
||||
}
|
||||
else if (options.TransactionalContentHash.Value().Algorithm == HashAlgorithm::Crc64)
|
||||
{
|
||||
request.SetHeader(
|
||||
"x-ms-source-content-crc64",
|
||||
Azure::Core::Convert::Base64Encode(
|
||||
options.TransactionalContentHash.Value().Value));
|
||||
}
|
||||
}
|
||||
auto pHttpResponse = pipeline.Send(request, context);
|
||||
Azure::Core::Http::RawResponse& httpResponse = *pHttpResponse;
|
||||
CopyBlobFromUriResult response;
|
||||
auto http_status_code = httpResponse.GetStatusCode();
|
||||
if (http_status_code != Azure::Core::Http::HttpStatusCode::Accepted)
|
||||
{
|
||||
throw StorageException::CreateFromResponse(std::move(pHttpResponse));
|
||||
}
|
||||
response.ETag = Azure::ETag(httpResponse.GetHeaders().at("etag"));
|
||||
response.LastModified = Azure::DateTime::Parse(
|
||||
httpResponse.GetHeaders().at("last-modified"), Azure::DateTime::DateFormat::Rfc1123);
|
||||
response.CopyId = httpResponse.GetHeaders().at("x-ms-copy-id");
|
||||
response.CopyStatus = CopyStatus(httpResponse.GetHeaders().at("x-ms-copy-status"));
|
||||
auto x_ms_version_id__iterator = httpResponse.GetHeaders().find("x-ms-version-id");
|
||||
if (x_ms_version_id__iterator != httpResponse.GetHeaders().end())
|
||||
{
|
||||
response.VersionId = x_ms_version_id__iterator->second;
|
||||
}
|
||||
return Azure::Response<CopyBlobFromUriResult>(
|
||||
std::move(response), std::move(pHttpResponse));
|
||||
}
|
||||
|
||||
struct StartBlobCopyFromUriOptions final
|
||||
{
|
||||
Azure::Nullable<int32_t> Timeout;
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
|
||||
#include "azure/storage/blobs/blob_client.hpp"
|
||||
|
||||
#include <azure/core/azure_assert.hpp>
|
||||
#include <azure/core/http/policies/policy.hpp>
|
||||
#include <azure/storage/common/internal/concurrent_transfer.hpp>
|
||||
#include <azure/storage/common/internal/constants.hpp>
|
||||
@ -531,6 +532,38 @@ namespace Azure { namespace Storage { namespace Blobs {
|
||||
*m_pipeline, m_blobUrl, protocolLayerOptions, context);
|
||||
}
|
||||
|
||||
Azure::Response<Models::CopyBlobFromUriResult> BlobClient::CopyFromUri(
|
||||
const std::string& sourceUri,
|
||||
const CopyBlobFromUriOptions& options,
|
||||
const Azure::Core::Context& context) const
|
||||
{
|
||||
_detail::BlobRestClient::Blob::CopyBlobFromUriOptions protocolLayerOptions;
|
||||
protocolLayerOptions.Metadata = options.Metadata;
|
||||
protocolLayerOptions.Tags = options.Tags;
|
||||
protocolLayerOptions.SourceUri = sourceUri;
|
||||
protocolLayerOptions.AccessTier = options.AccessTier;
|
||||
protocolLayerOptions.LeaseId = options.AccessConditions.LeaseId;
|
||||
protocolLayerOptions.IfModifiedSince = options.AccessConditions.IfModifiedSince;
|
||||
protocolLayerOptions.IfUnmodifiedSince = options.AccessConditions.IfUnmodifiedSince;
|
||||
protocolLayerOptions.IfMatch = options.AccessConditions.IfMatch;
|
||||
protocolLayerOptions.IfNoneMatch = options.AccessConditions.IfNoneMatch;
|
||||
protocolLayerOptions.IfTags = options.AccessConditions.TagConditions;
|
||||
protocolLayerOptions.SourceIfModifiedSince = options.SourceAccessConditions.IfModifiedSince;
|
||||
protocolLayerOptions.SourceIfUnmodifiedSince = options.SourceAccessConditions.IfUnmodifiedSince;
|
||||
protocolLayerOptions.SourceIfMatch = options.SourceAccessConditions.IfMatch;
|
||||
protocolLayerOptions.SourceIfNoneMatch = options.SourceAccessConditions.IfNoneMatch;
|
||||
if (options.TransactionalContentHash.HasValue())
|
||||
{
|
||||
AZURE_ASSERT_MSG(
|
||||
options.TransactionalContentHash.Value().Algorithm == HashAlgorithm::Md5,
|
||||
"This operation only supports MD5 transactional content hash.");
|
||||
protocolLayerOptions.TransactionalContentHash = options.TransactionalContentHash;
|
||||
}
|
||||
|
||||
return _detail::BlobRestClient::Blob::CopyFromUri(
|
||||
*m_pipeline, m_blobUrl, protocolLayerOptions, context);
|
||||
}
|
||||
|
||||
StartBlobCopyOperation BlobClient::StartCopyFromUri(
|
||||
const std::string& sourceUri,
|
||||
const StartBlobCopyFromUriOptions& options,
|
||||
|
||||
@ -210,53 +210,6 @@ namespace Azure { namespace Storage { namespace Test {
|
||||
EXPECT_NO_THROW(appendBlobClient.Delete(options));
|
||||
}
|
||||
|
||||
TEST_F(AppendBlobClientTest, SourceBlobAccessConditions)
|
||||
{
|
||||
auto sourceBlobClient = Azure::Storage::Blobs::AppendBlobClient::CreateFromConnectionString(
|
||||
StandardStorageConnectionString(), m_containerName, RandomString());
|
||||
auto createResponse = sourceBlobClient.Create();
|
||||
Azure::ETag eTag = createResponse.Value.ETag;
|
||||
auto lastModifiedTime = createResponse.Value.LastModified;
|
||||
auto timeBeforeStr = lastModifiedTime - std::chrono::seconds(1);
|
||||
auto timeAfterStr = lastModifiedTime + std::chrono::seconds(1);
|
||||
|
||||
auto destBlobClient = Azure::Storage::Blobs::AppendBlobClient::CreateFromConnectionString(
|
||||
StandardStorageConnectionString(), m_containerName, RandomString());
|
||||
|
||||
{
|
||||
Blobs::StartBlobCopyFromUriOptions options;
|
||||
options.SourceAccessConditions.IfMatch = eTag;
|
||||
EXPECT_NO_THROW(destBlobClient.StartCopyFromUri(sourceBlobClient.GetUrl(), options));
|
||||
options.SourceAccessConditions.IfMatch = DummyETag;
|
||||
EXPECT_THROW(
|
||||
destBlobClient.StartCopyFromUri(sourceBlobClient.GetUrl(), options), StorageException);
|
||||
}
|
||||
{
|
||||
Blobs::StartBlobCopyFromUriOptions options;
|
||||
options.SourceAccessConditions.IfNoneMatch = DummyETag;
|
||||
EXPECT_NO_THROW(destBlobClient.StartCopyFromUri(sourceBlobClient.GetUrl(), options));
|
||||
options.SourceAccessConditions.IfNoneMatch = eTag;
|
||||
EXPECT_THROW(
|
||||
destBlobClient.StartCopyFromUri(sourceBlobClient.GetUrl(), options), StorageException);
|
||||
}
|
||||
{
|
||||
Blobs::StartBlobCopyFromUriOptions options;
|
||||
options.SourceAccessConditions.IfModifiedSince = timeBeforeStr;
|
||||
EXPECT_NO_THROW(destBlobClient.StartCopyFromUri(sourceBlobClient.GetUrl(), options));
|
||||
options.SourceAccessConditions.IfModifiedSince = timeAfterStr;
|
||||
EXPECT_THROW(
|
||||
destBlobClient.StartCopyFromUri(sourceBlobClient.GetUrl(), options), StorageException);
|
||||
}
|
||||
{
|
||||
Blobs::StartBlobCopyFromUriOptions options;
|
||||
options.SourceAccessConditions.IfUnmodifiedSince = timeAfterStr;
|
||||
EXPECT_NO_THROW(destBlobClient.StartCopyFromUri(sourceBlobClient.GetUrl(), options));
|
||||
options.SourceAccessConditions.IfUnmodifiedSince = timeBeforeStr;
|
||||
EXPECT_THROW(
|
||||
destBlobClient.StartCopyFromUri(sourceBlobClient.GetUrl(), options), StorageException);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(AppendBlobClientTest, Seal)
|
||||
{
|
||||
std::string blobName = RandomString();
|
||||
|
||||
@ -1127,6 +1127,38 @@ namespace Azure { namespace Storage { namespace Test {
|
||||
options.AccessConditions.TagConditions = successWhereExpression;
|
||||
EXPECT_NO_THROW(blockBlobClient.GetBlockList(options));
|
||||
}
|
||||
|
||||
{
|
||||
auto sourceBlobClient = Azure::Storage::Blobs::BlockBlobClient::CreateFromConnectionString(
|
||||
StandardStorageConnectionString(), m_containerName, RandomString());
|
||||
std::vector<uint8_t> buffer;
|
||||
buffer.resize(1024);
|
||||
sourceBlobClient.UploadFrom(buffer.data(), buffer.size());
|
||||
|
||||
Blobs::CopyBlobFromUriOptions options;
|
||||
options.AccessConditions.TagConditions = failWhereExpression;
|
||||
EXPECT_THROW(
|
||||
blockBlobClient.CopyFromUri(sourceBlobClient.GetUrl() + GetSas(), options),
|
||||
StorageException);
|
||||
options.AccessConditions.TagConditions = successWhereExpression;
|
||||
EXPECT_NO_THROW(blockBlobClient.CopyFromUri(sourceBlobClient.GetUrl() + GetSas(), options));
|
||||
}
|
||||
|
||||
{
|
||||
auto sourceBlobClient = Azure::Storage::Blobs::BlockBlobClient::CreateFromConnectionString(
|
||||
StandardStorageConnectionString(), m_containerName, RandomString());
|
||||
std::vector<uint8_t> buffer;
|
||||
buffer.resize(1024);
|
||||
sourceBlobClient.UploadFrom(buffer.data(), buffer.size());
|
||||
sourceBlobClient.SetTags(tags);
|
||||
|
||||
Blobs::StartBlobCopyFromUriOptions options;
|
||||
options.SourceAccessConditions.TagConditions = failWhereExpression;
|
||||
EXPECT_THROW(
|
||||
blockBlobClient.StartCopyFromUri(sourceBlobClient.GetUrl(), options), StorageException);
|
||||
options.SourceAccessConditions.TagConditions = successWhereExpression;
|
||||
EXPECT_NO_THROW(blockBlobClient.StartCopyFromUri(sourceBlobClient.GetUrl(), options));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(BlobContainerClientTest, SpecialBlobName)
|
||||
|
||||
@ -314,7 +314,39 @@ namespace Azure { namespace Storage { namespace Test {
|
||||
EXPECT_THROW(blockBlobClient.Download(options), StorageException);
|
||||
}
|
||||
|
||||
TEST_F(BlockBlobClientTest, CopyFromUri)
|
||||
TEST_F(BlockBlobClientTest, SyncCopyFromUri)
|
||||
{
|
||||
const std::string blobName = RandomString();
|
||||
auto blobClient = m_blobContainerClient->GetBlobClient(blobName);
|
||||
auto res = blobClient.CopyFromUri(m_blockBlobClient->GetUrl() + GetSas());
|
||||
EXPECT_EQ(res.RawResponse->GetStatusCode(), Azure::Core::Http::HttpStatusCode::Accepted);
|
||||
EXPECT_TRUE(res.Value.ETag.HasValue());
|
||||
EXPECT_TRUE(IsValidTime(res.Value.LastModified));
|
||||
EXPECT_FALSE(res.Value.CopyId.empty());
|
||||
EXPECT_EQ(res.Value.CopyStatus, Azure::Storage::Blobs::Models::CopyStatus::Success);
|
||||
|
||||
auto downloadResult = blobClient.Download();
|
||||
EXPECT_FALSE(downloadResult.Value.Details.CopyId.Value().empty());
|
||||
EXPECT_FALSE(downloadResult.Value.Details.CopySource.Value().empty());
|
||||
EXPECT_TRUE(
|
||||
downloadResult.Value.Details.CopyStatus.Value()
|
||||
== Azure::Storage::Blobs::Models::CopyStatus::Success);
|
||||
EXPECT_FALSE(downloadResult.Value.Details.CopyProgress.Value().empty());
|
||||
EXPECT_TRUE(IsValidTime(downloadResult.Value.Details.CopyCompletedOn.Value()));
|
||||
|
||||
auto blobItem = GetBlobItem(blobName, Blobs::Models::ListBlobsIncludeFlags::Copy);
|
||||
EXPECT_FALSE(blobItem.Details.CopyId.Value().empty());
|
||||
EXPECT_FALSE(blobItem.Details.CopySource.Value().empty());
|
||||
EXPECT_TRUE(
|
||||
blobItem.Details.CopyStatus.Value() == Azure::Storage::Blobs::Models::CopyStatus::Success);
|
||||
EXPECT_FALSE(blobItem.Details.CopyProgress.Value().empty());
|
||||
EXPECT_TRUE(IsValidTime(blobItem.Details.CopyCompletedOn.Value()));
|
||||
ASSERT_TRUE(blobItem.Details.IsIncrementalCopy.HasValue());
|
||||
EXPECT_FALSE(blobItem.Details.IsIncrementalCopy.Value());
|
||||
EXPECT_FALSE(blobItem.Details.IncrementalCopyDestinationSnapshot.HasValue());
|
||||
}
|
||||
|
||||
TEST_F(BlockBlobClientTest, AsyncCopyFromUri)
|
||||
{
|
||||
const std::string blobName = RandomString();
|
||||
auto blobClient = m_blobContainerClient->GetBlobClient(blobName);
|
||||
@ -353,15 +385,32 @@ namespace Azure { namespace Storage { namespace Test {
|
||||
EXPECT_FALSE(blobItem.Details.IncrementalCopyDestinationSnapshot.HasValue());
|
||||
}
|
||||
|
||||
TEST_F(BlockBlobClientTest, CopyWithTags)
|
||||
TEST_F(BlockBlobClientTest, CopyWithTagsMetadataTier)
|
||||
{
|
||||
auto blobClient = m_blobContainerClient->GetBlockBlobClient(RandomString());
|
||||
Blobs::StartBlobCopyFromUriOptions options;
|
||||
options.Tags["key1"] = "value1";
|
||||
options.Tags["key2"] = "value2";
|
||||
options.Tags["key3 +-./:=_"] = "v1 +-./:=_";
|
||||
blobClient.StartCopyFromUri(m_blockBlobClient->GetUrl(), options);
|
||||
options.Metadata["key1"] = "value1";
|
||||
options.Metadata["key2"] = "value2";
|
||||
options.AccessTier = Blobs::Models::AccessTier::Cool;
|
||||
auto operation = blobClient.StartCopyFromUri(m_blockBlobClient->GetUrl(), options);
|
||||
operation.PollUntilDone(std::chrono::seconds(1));
|
||||
EXPECT_EQ(blobClient.GetTags().Value, options.Tags);
|
||||
auto properties = blobClient.GetProperties().Value;
|
||||
EXPECT_EQ(properties.Metadata, options.Metadata);
|
||||
EXPECT_EQ(properties.AccessTier.Value(), options.AccessTier.Value());
|
||||
|
||||
Blobs::CopyBlobFromUriOptions options2;
|
||||
options2.Tags = options.Tags;
|
||||
options2.Metadata = options.Metadata;
|
||||
options2.AccessTier = options.AccessTier;
|
||||
blobClient.CopyFromUri(m_blockBlobClient->GetUrl() + GetSas(), options2);
|
||||
EXPECT_EQ(blobClient.GetTags().Value, options2.Tags);
|
||||
properties = blobClient.GetProperties().Value;
|
||||
EXPECT_EQ(properties.Metadata, options2.Metadata);
|
||||
EXPECT_EQ(properties.AccessTier.Value(), options2.AccessTier.Value());
|
||||
}
|
||||
|
||||
TEST_F(BlockBlobClientTest, SnapShotVersions)
|
||||
@ -1170,4 +1219,127 @@ namespace Azure { namespace Storage { namespace Test {
|
||||
EXPECT_EQ(blobItem.BlobSize, 0);
|
||||
}
|
||||
|
||||
TEST_F(BlobContainerClientTest, SourceTagsConditions)
|
||||
{
|
||||
auto sourceBlobClient = Azure::Storage::Blobs::BlockBlobClient::CreateFromConnectionString(
|
||||
StandardStorageConnectionString(), m_containerName, RandomString());
|
||||
std::vector<uint8_t> buffer;
|
||||
buffer.resize(1024);
|
||||
}
|
||||
|
||||
TEST_F(BlobContainerClientTest, SourceBlobAccessConditions)
|
||||
{
|
||||
auto sourceBlobClient = Azure::Storage::Blobs::BlockBlobClient::CreateFromConnectionString(
|
||||
StandardStorageConnectionString(), m_containerName, RandomString());
|
||||
std::vector<uint8_t> buffer;
|
||||
buffer.resize(1024);
|
||||
auto createResponse = sourceBlobClient.UploadFrom(buffer.data(), buffer.size());
|
||||
Azure::ETag eTag = createResponse.Value.ETag;
|
||||
auto lastModifiedTime = createResponse.Value.LastModified;
|
||||
auto timeBeforeStr = lastModifiedTime - std::chrono::seconds(2);
|
||||
auto timeAfterStr = lastModifiedTime + std::chrono::seconds(2);
|
||||
|
||||
auto destBlobClient = Azure::Storage::Blobs::BlockBlobClient::CreateFromConnectionString(
|
||||
StandardStorageConnectionString(), m_containerName, RandomString());
|
||||
|
||||
{
|
||||
Blobs::StartBlobCopyFromUriOptions options;
|
||||
options.SourceAccessConditions.IfMatch = eTag;
|
||||
EXPECT_NO_THROW(destBlobClient.StartCopyFromUri(sourceBlobClient.GetUrl(), options));
|
||||
options.SourceAccessConditions.IfMatch = DummyETag;
|
||||
EXPECT_THROW(
|
||||
destBlobClient.StartCopyFromUri(sourceBlobClient.GetUrl(), options), StorageException);
|
||||
|
||||
Blobs::CopyBlobFromUriOptions options2;
|
||||
options2.SourceAccessConditions.IfMatch = eTag;
|
||||
EXPECT_NO_THROW(destBlobClient.CopyFromUri(sourceBlobClient.GetUrl() + GetSas(), options2));
|
||||
options2.SourceAccessConditions.IfMatch = DummyETag;
|
||||
EXPECT_THROW(
|
||||
destBlobClient.CopyFromUri(sourceBlobClient.GetUrl() + GetSas(), options2),
|
||||
StorageException);
|
||||
}
|
||||
{
|
||||
Blobs::StartBlobCopyFromUriOptions options;
|
||||
options.SourceAccessConditions.IfNoneMatch = DummyETag;
|
||||
EXPECT_NO_THROW(destBlobClient.StartCopyFromUri(sourceBlobClient.GetUrl(), options));
|
||||
options.SourceAccessConditions.IfNoneMatch = eTag;
|
||||
EXPECT_THROW(
|
||||
destBlobClient.StartCopyFromUri(sourceBlobClient.GetUrl(), options), StorageException);
|
||||
|
||||
Blobs::CopyBlobFromUriOptions options2;
|
||||
options2.SourceAccessConditions.IfNoneMatch = DummyETag;
|
||||
EXPECT_NO_THROW(destBlobClient.CopyFromUri(sourceBlobClient.GetUrl() + GetSas(), options2));
|
||||
options2.SourceAccessConditions.IfNoneMatch = eTag;
|
||||
EXPECT_THROW(
|
||||
destBlobClient.CopyFromUri(sourceBlobClient.GetUrl() + GetSas(), options2),
|
||||
StorageException);
|
||||
}
|
||||
{
|
||||
Blobs::StartBlobCopyFromUriOptions options;
|
||||
options.SourceAccessConditions.IfModifiedSince = timeBeforeStr;
|
||||
EXPECT_NO_THROW(destBlobClient.StartCopyFromUri(sourceBlobClient.GetUrl(), options));
|
||||
options.SourceAccessConditions.IfModifiedSince = timeAfterStr;
|
||||
EXPECT_THROW(
|
||||
destBlobClient.StartCopyFromUri(sourceBlobClient.GetUrl(), options), StorageException);
|
||||
|
||||
sourceBlobClient.GetProperties();
|
||||
Blobs::CopyBlobFromUriOptions options2;
|
||||
options2.SourceAccessConditions.IfModifiedSince = timeBeforeStr;
|
||||
EXPECT_NO_THROW(destBlobClient.CopyFromUri(sourceBlobClient.GetUrl() + GetSas(), options2));
|
||||
options2.SourceAccessConditions.IfModifiedSince = timeAfterStr;
|
||||
EXPECT_THROW(
|
||||
destBlobClient.CopyFromUri(sourceBlobClient.GetUrl() + GetSas(), options2),
|
||||
StorageException);
|
||||
}
|
||||
{
|
||||
Blobs::StartBlobCopyFromUriOptions options;
|
||||
options.SourceAccessConditions.IfUnmodifiedSince = timeAfterStr;
|
||||
EXPECT_NO_THROW(destBlobClient.StartCopyFromUri(sourceBlobClient.GetUrl(), options));
|
||||
options.SourceAccessConditions.IfUnmodifiedSince = timeBeforeStr;
|
||||
EXPECT_THROW(
|
||||
destBlobClient.StartCopyFromUri(sourceBlobClient.GetUrl(), options), StorageException);
|
||||
|
||||
Blobs::CopyBlobFromUriOptions options2;
|
||||
options2.SourceAccessConditions.IfUnmodifiedSince = timeAfterStr;
|
||||
EXPECT_NO_THROW(destBlobClient.CopyFromUri(sourceBlobClient.GetUrl() + GetSas(), options2));
|
||||
options2.SourceAccessConditions.IfUnmodifiedSince = timeBeforeStr;
|
||||
EXPECT_THROW(
|
||||
destBlobClient.CopyFromUri(sourceBlobClient.GetUrl() + GetSas(), options2),
|
||||
StorageException);
|
||||
}
|
||||
|
||||
// lease
|
||||
{
|
||||
const std::string leaseId = Blobs::BlobLeaseClient::CreateUniqueLeaseId();
|
||||
const std::string dummyLeaseId = Blobs::BlobLeaseClient::CreateUniqueLeaseId();
|
||||
Blobs::BlobLeaseClient leaseClient(destBlobClient, leaseId);
|
||||
|
||||
leaseClient.Acquire(std::chrono::seconds(60));
|
||||
|
||||
Blobs::CopyBlobFromUriOptions options;
|
||||
options.AccessConditions.LeaseId = dummyLeaseId;
|
||||
EXPECT_THROW(
|
||||
destBlobClient.CopyFromUri(sourceBlobClient.GetUrl() + GetSas(), options),
|
||||
StorageException);
|
||||
options.AccessConditions.LeaseId = leaseId;
|
||||
EXPECT_NO_THROW(destBlobClient.CopyFromUri(sourceBlobClient.GetUrl() + GetSas(), options));
|
||||
leaseClient.Release();
|
||||
}
|
||||
|
||||
// content md5
|
||||
{
|
||||
const auto hash = sourceBlobClient.GetProperties().Value.HttpHeaders.ContentHash;
|
||||
ASSERT_FALSE(hash.Value.empty());
|
||||
|
||||
Blobs::CopyBlobFromUriOptions options;
|
||||
options.TransactionalContentHash = hash;
|
||||
options.TransactionalContentHash.Value().Value = Azure::Core::Convert::Base64Decode(DummyMd5);
|
||||
EXPECT_THROW(
|
||||
destBlobClient.CopyFromUri(sourceBlobClient.GetUrl() + GetSas(), options),
|
||||
StorageException);
|
||||
options.TransactionalContentHash = hash;
|
||||
EXPECT_NO_THROW(destBlobClient.CopyFromUri(sourceBlobClient.GetUrl() + GetSas(), options));
|
||||
}
|
||||
}
|
||||
|
||||
}}} // namespace Azure::Storage::Test
|
||||
|
||||
Loading…
Reference in New Issue
Block a user