From f9157c8763524a476e2194a5c20fafc1eeb51d4c Mon Sep 17 00:00:00 2001 From: JinmingHu Date: Wed, 17 Nov 2021 10:07:40 +0800 Subject: [PATCH] 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 * Update sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/blob_client.hpp Co-authored-by: Ahson Khan * Update sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/blob_client.hpp Co-authored-by: Ahson Khan Co-authored-by: Ahson Khan --- sdk/storage/azure-storage-blobs/CHANGELOG.md | 12 ++ .../inc/azure/storage/blobs/blob_client.hpp | 17 ++ .../inc/azure/storage/blobs/blob_options.hpp | 46 +++++ .../blobs/protocol/blob_rest_client.hpp | 182 ++++++++++++++++++ .../azure-storage-blobs/src/blob_client.cpp | 33 ++++ .../test/ut/append_blob_client_test.cpp | 47 ----- .../test/ut/blob_container_client_test.cpp | 32 +++ .../test/ut/block_blob_client_test.cpp | 178 ++++++++++++++++- 8 files changed, 497 insertions(+), 50 deletions(-) diff --git a/sdk/storage/azure-storage-blobs/CHANGELOG.md b/sdk/storage/azure-storage-blobs/CHANGELOG.md index d1519ac17..30d0d108e 100644 --- a/sdk/storage/azure-storage-blobs/CHANGELOG.md +++ b/sdk/storage/azure-storage-blobs/CHANGELOG.md @@ -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 diff --git a/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/blob_client.hpp b/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/blob_client.hpp index b7e47a46a..e8bc98847 100644 --- a/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/blob_client.hpp +++ b/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/blob_client.hpp @@ -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 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. * diff --git a/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/blob_options.hpp b/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/blob_options.hpp index d9b57b4d4..394f34989 100644 --- a/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/blob_options.hpp +++ b/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/blob_options.hpp @@ -497,6 +497,52 @@ namespace Azure { namespace Storage { namespace Blobs { Azure::Nullable 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 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 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 TransactionalContentHash; + }; + /** * @brief Optional parameters for #Azure::Storage::Blobs::BlobClient::AbortCopyFromUri. */ diff --git a/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/protocol/blob_rest_client.hpp b/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/protocol/blob_rest_client.hpp index ff5ef5613..4c655ca57 100644 --- a/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/protocol/blob_rest_client.hpp +++ b/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/protocol/blob_rest_client.hpp @@ -1635,6 +1635,41 @@ namespace Azure { namespace Storage { namespace Blobs { Azure::Nullable 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 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 Timeout; + Storage::Metadata Metadata; + std::map Tags; + std::string SourceUri; + Azure::Nullable LeaseId; + Azure::Nullable AccessTier; + Azure::Nullable IfModifiedSince; + Azure::Nullable IfUnmodifiedSince; + Azure::ETag IfMatch; + Azure::ETag IfNoneMatch; + Azure::Nullable IfTags; + Azure::Nullable SourceIfModifiedSince; + Azure::Nullable SourceIfUnmodifiedSince; + Azure::ETag SourceIfMatch; + Azure::ETag SourceIfNoneMatch; + Azure::Nullable TransactionalContentHash; + }; // struct CopyBlobFromUriOptions + + static Azure::Response 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( + std::move(response), std::move(pHttpResponse)); + } + struct StartBlobCopyFromUriOptions final { Azure::Nullable Timeout; diff --git a/sdk/storage/azure-storage-blobs/src/blob_client.cpp b/sdk/storage/azure-storage-blobs/src/blob_client.cpp index 8456ab960..8b1756dfc 100644 --- a/sdk/storage/azure-storage-blobs/src/blob_client.cpp +++ b/sdk/storage/azure-storage-blobs/src/blob_client.cpp @@ -3,6 +3,7 @@ #include "azure/storage/blobs/blob_client.hpp" +#include #include #include #include @@ -531,6 +532,38 @@ namespace Azure { namespace Storage { namespace Blobs { *m_pipeline, m_blobUrl, protocolLayerOptions, context); } + Azure::Response 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, diff --git a/sdk/storage/azure-storage-blobs/test/ut/append_blob_client_test.cpp b/sdk/storage/azure-storage-blobs/test/ut/append_blob_client_test.cpp index 2ee49e123..a5c5c6593 100644 --- a/sdk/storage/azure-storage-blobs/test/ut/append_blob_client_test.cpp +++ b/sdk/storage/azure-storage-blobs/test/ut/append_blob_client_test.cpp @@ -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(); diff --git a/sdk/storage/azure-storage-blobs/test/ut/blob_container_client_test.cpp b/sdk/storage/azure-storage-blobs/test/ut/blob_container_client_test.cpp index a68776908..9b41d33ac 100644 --- a/sdk/storage/azure-storage-blobs/test/ut/blob_container_client_test.cpp +++ b/sdk/storage/azure-storage-blobs/test/ut/blob_container_client_test.cpp @@ -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 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 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) diff --git a/sdk/storage/azure-storage-blobs/test/ut/block_blob_client_test.cpp b/sdk/storage/azure-storage-blobs/test/ut/block_blob_client_test.cpp index 8a508a617..32e454b56 100644 --- a/sdk/storage/azure-storage-blobs/test/ut/block_blob_client_test.cpp +++ b/sdk/storage/azure-storage-blobs/test/ut/block_blob_client_test.cpp @@ -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 buffer; + buffer.resize(1024); + } + + TEST_F(BlobContainerClientTest, SourceBlobAccessConditions) + { + auto sourceBlobClient = Azure::Storage::Blobs::BlockBlobClient::CreateFromConnectionString( + StandardStorageConnectionString(), m_containerName, RandomString()); + std::vector 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