diff --git a/sdk/storage/azure-storage-blobs/CHANGELOG.md b/sdk/storage/azure-storage-blobs/CHANGELOG.md index a06ce1ceb..235e4d7ac 100644 --- a/sdk/storage/azure-storage-blobs/CHANGELOG.md +++ b/sdk/storage/azure-storage-blobs/CHANGELOG.md @@ -5,6 +5,7 @@ ### New Features - Added `RequestId` in API return types. +- Added some new properties in `GetBlobPropertiesResult` and `DownloadBlobResult`. ### Breaking Changes 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 a7a2056dc..4c2e3dfeb 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 @@ -1067,6 +1067,14 @@ namespace Azure { namespace Storage { namespace Blobs { std::vector ObjectReplicationSourceProperties; // only valid for replication source blob Azure::Core::Nullable TagCount; + Azure::Core::Nullable CopyId; + Azure::Core::Nullable CopySource; + Azure::Core::Nullable CopyStatus; + Azure::Core::Nullable CopyStatusDescription; + Azure::Core::Nullable CopyProgress; + Azure::Core::Nullable CopyCompletedOn; + Azure::Core::Nullable VersionId; + Azure::Core::Nullable IsCurrentVersion; }; // struct DownloadBlobResult struct GetBlobPropertiesResult @@ -1093,10 +1101,14 @@ namespace Azure { namespace Storage { namespace Blobs { Azure::Core::Nullable Tier; Azure::Core::Nullable IsAccessTierInferred; Azure::Core::Nullable ArchiveStatus; + Azure::Core::Nullable RehydratePriority; Azure::Core::Nullable AccessTierChangedOn; Azure::Core::Nullable CopyId; Azure::Core::Nullable CopySource; Azure::Core::Nullable CopyStatus; + Azure::Core::Nullable CopyStatusDescription; + Azure::Core::Nullable IsIncrementalCopy; + Azure::Core::Nullable IncrementalCopyDestinationSnapshot; Azure::Core::Nullable CopyProgress; Azure::Core::Nullable CopyCompletedOn; Azure::Core::Nullable @@ -1104,6 +1116,8 @@ namespace Azure { namespace Storage { namespace Blobs { std::vector ObjectReplicationSourceProperties; // only valid for replication source blob Azure::Core::Nullable TagCount; + Azure::Core::Nullable VersionId; + Azure::Core::Nullable IsCurrentVersion; }; // struct GetBlobPropertiesResult struct ListBlobsByHierarchySinglePageResult @@ -5101,6 +5115,51 @@ namespace Azure { namespace Storage { namespace Blobs { { response.TagCount = std::stoi(x_ms_tag_count__iterator->second); } + auto x_ms_copy_id__iterator = httpResponse.GetHeaders().find("x-ms-copy-id"); + if (x_ms_copy_id__iterator != httpResponse.GetHeaders().end()) + { + response.CopyId = x_ms_copy_id__iterator->second; + } + auto x_ms_copy_source__iterator = httpResponse.GetHeaders().find("x-ms-copy-source"); + if (x_ms_copy_source__iterator != httpResponse.GetHeaders().end()) + { + response.CopySource = x_ms_copy_source__iterator->second; + } + auto x_ms_copy_status__iterator = httpResponse.GetHeaders().find("x-ms-copy-status"); + if (x_ms_copy_status__iterator != httpResponse.GetHeaders().end()) + { + response.CopyStatus = CopyStatus(x_ms_copy_status__iterator->second); + } + auto x_ms_copy_status_description__iterator + = httpResponse.GetHeaders().find("x-ms-copy-status-description"); + if (x_ms_copy_status_description__iterator != httpResponse.GetHeaders().end()) + { + response.CopyStatusDescription = x_ms_copy_status_description__iterator->second; + } + auto x_ms_copy_progress__iterator = httpResponse.GetHeaders().find("x-ms-copy-progress"); + if (x_ms_copy_progress__iterator != httpResponse.GetHeaders().end()) + { + response.CopyProgress = x_ms_copy_progress__iterator->second; + } + auto x_ms_copy_completion_time__iterator + = httpResponse.GetHeaders().find("x-ms-copy-completion-time"); + if (x_ms_copy_completion_time__iterator != httpResponse.GetHeaders().end()) + { + response.CopyCompletedOn = Azure::Core::DateTime::Parse( + x_ms_copy_completion_time__iterator->second, + Azure::Core::DateTime::DateFormat::Rfc1123); + } + 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; + } + auto x_ms_is_current_version__iterator + = httpResponse.GetHeaders().find("x-ms-is-current-version"); + if (x_ms_is_current_version__iterator != httpResponse.GetHeaders().end()) + { + response.IsCurrentVersion = x_ms_is_current_version__iterator->second == "true"; + } return Azure::Core::Response( std::move(response), std::move(pHttpResponse)); } @@ -5493,6 +5552,13 @@ namespace Azure { namespace Storage { namespace Blobs { { response.ArchiveStatus = BlobArchiveStatus(x_ms_archive_status__iterator->second); } + auto x_ms_rehydrate_priority__iterator + = httpResponse.GetHeaders().find("x-ms-rehydrate-priority"); + if (x_ms_rehydrate_priority__iterator != httpResponse.GetHeaders().end()) + { + response.RehydratePriority + = RehydratePriority(x_ms_rehydrate_priority__iterator->second); + } auto x_ms_access_tier_change_time__iterator = httpResponse.GetHeaders().find("x-ms-access-tier-change-time"); if (x_ms_access_tier_change_time__iterator != httpResponse.GetHeaders().end()) @@ -5516,6 +5582,25 @@ namespace Azure { namespace Storage { namespace Blobs { { response.CopyStatus = CopyStatus(x_ms_copy_status__iterator->second); } + auto x_ms_copy_status_description__iterator + = httpResponse.GetHeaders().find("x-ms-copy-status-description"); + if (x_ms_copy_status_description__iterator != httpResponse.GetHeaders().end()) + { + response.CopyStatusDescription = x_ms_copy_status_description__iterator->second; + } + auto x_ms_incremental_copy__iterator + = httpResponse.GetHeaders().find("x-ms-incremental-copy"); + if (x_ms_incremental_copy__iterator != httpResponse.GetHeaders().end()) + { + response.IsIncrementalCopy = x_ms_incremental_copy__iterator->second == "true"; + } + auto x_ms_copy_destination_snapshot__iterator + = httpResponse.GetHeaders().find("x-ms-copy-destination-snapshot"); + if (x_ms_copy_destination_snapshot__iterator != httpResponse.GetHeaders().end()) + { + response.IncrementalCopyDestinationSnapshot + = x_ms_copy_destination_snapshot__iterator->second; + } auto x_ms_copy_progress__iterator = httpResponse.GetHeaders().find("x-ms-copy-progress"); if (x_ms_copy_progress__iterator != httpResponse.GetHeaders().end()) { @@ -5568,6 +5653,17 @@ namespace Azure { namespace Storage { namespace Blobs { { response.TagCount = std::stoi(x_ms_tag_count__iterator->second); } + 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; + } + auto x_ms_is_current_version__iterator + = httpResponse.GetHeaders().find("x-ms-is-current-version"); + if (x_ms_is_current_version__iterator != httpResponse.GetHeaders().end()) + { + response.IsCurrentVersion = x_ms_is_current_version__iterator->second == "true"; + } return Azure::Core::Response( std::move(response), std::move(pHttpResponse)); } diff --git a/sdk/storage/azure-storage-blobs/src/blob_client.cpp b/sdk/storage/azure-storage-blobs/src/blob_client.cpp index 7a6198e8f..c9672e41d 100644 --- a/sdk/storage/azure-storage-blobs/src/blob_client.cpp +++ b/sdk/storage/azure-storage-blobs/src/blob_client.cpp @@ -208,6 +208,15 @@ namespace Azure { namespace Storage { namespace Blobs { downloadResponse->BodyStream = std::make_unique( std::move(downloadResponse->BodyStream), reliableStreamOptions, retryFunction); } + if (downloadResponse->BlobType == Models::BlobType::AppendBlob + && !downloadResponse->IsSealed.HasValue()) + { + downloadResponse->IsSealed = false; + } + if (downloadResponse->VersionId.HasValue() && !downloadResponse->IsCurrentVersion.HasValue()) + { + downloadResponse->IsCurrentVersion = false; + } return downloadResponse; } @@ -463,8 +472,25 @@ namespace Azure { namespace Storage { namespace Blobs { protocolLayerOptions.EncryptionKeySha256 = m_customerProvidedKey.GetValue().KeyHash; protocolLayerOptions.EncryptionAlgorithm = m_customerProvidedKey.GetValue().Algorithm; } - return Details::BlobRestClient::Blob::GetProperties( + auto response = Details::BlobRestClient::Blob::GetProperties( options.Context, *m_pipeline, m_blobUrl, protocolLayerOptions); + if (response->Tier.HasValue() && !response->IsAccessTierInferred.HasValue()) + { + response->IsAccessTierInferred = false; + } + if (response->VersionId.HasValue() && !response->IsCurrentVersion.HasValue()) + { + response->IsCurrentVersion = false; + } + if (response->CopyStatus.HasValue() && !response->IsIncrementalCopy.HasValue()) + { + response->IsIncrementalCopy = false; + } + if (response->BlobType == Models::BlobType::AppendBlob && !response->IsSealed.HasValue()) + { + response->IsSealed = false; + } + return response; } Azure::Core::Response BlobClient::SetHttpHeaders( diff --git a/sdk/storage/azure-storage-blobs/src/blob_container_client.cpp b/sdk/storage/azure-storage-blobs/src/blob_container_client.cpp index 5c152655b..fff7325b7 100644 --- a/sdk/storage/azure-storage-blobs/src/blob_container_client.cpp +++ b/sdk/storage/azure-storage-blobs/src/blob_container_client.cpp @@ -239,10 +239,18 @@ namespace Azure { namespace Storage { namespace Blobs { options.Context, *m_pipeline, m_blobContainerUrl, protocolLayerOptions); for (auto& i : response->Items) { + if (i.Tier.HasValue() && !i.IsAccessTierInferred.HasValue()) + { + i.IsAccessTierInferred = false; + } if (i.VersionId.HasValue() && !i.IsCurrentVersion.HasValue()) { i.IsCurrentVersion = false; } + if (i.BlobType == Models::BlobType::AppendBlob && !i.IsSealed) + { + i.IsSealed = false; + } } return response; } diff --git a/sdk/storage/azure-storage-blobs/test/append_blob_client_test.cpp b/sdk/storage/azure-storage-blobs/test/append_blob_client_test.cpp index 2160119c7..81a0eb798 100644 --- a/sdk/storage/azure-storage-blobs/test/append_blob_client_test.cpp +++ b/sdk/storage/azure-storage-blobs/test/append_blob_client_test.cpp @@ -276,16 +276,28 @@ namespace Azure { namespace Storage { namespace Test { blobClient.AppendBlock(&blockContent); auto downloadResult = blobClient.Download(); - if (downloadResult->IsSealed.HasValue()) - { - EXPECT_FALSE(downloadResult->IsSealed.GetValue()); - } + EXPECT_TRUE(downloadResult->IsSealed.HasValue()); + EXPECT_FALSE(downloadResult->IsSealed.GetValue()); auto getPropertiesResult = blobClient.GetProperties(); - if (getPropertiesResult->IsSealed.HasValue()) + EXPECT_TRUE(getPropertiesResult->IsSealed.HasValue()); + EXPECT_FALSE(getPropertiesResult->IsSealed.GetValue()); + + Azure::Storage::Blobs::ListBlobsSinglePageOptions options; + options.Prefix = blobName; + do { - EXPECT_FALSE(getPropertiesResult->IsSealed.GetValue()); - } + auto res = m_blobContainerClient->ListBlobsSinglePage(options); + options.ContinuationToken = res->ContinuationToken; + for (const auto& blob : res->Items) + { + if (blob.Name == blobName) + { + EXPECT_TRUE(blob.IsSealed.HasValue()); + EXPECT_FALSE(blob.IsSealed.GetValue()); + } + } + } while (options.ContinuationToken.HasValue()); Blobs::SealAppendBlobOptions sealOptions; sealOptions.AccessConditions.IfAppendPositionEqual = m_blobContent.size() + 1; @@ -306,8 +318,6 @@ namespace Azure { namespace Storage { namespace Test { EXPECT_TRUE(getPropertiesResult->IsSealed.HasValue()); EXPECT_TRUE(getPropertiesResult->IsSealed.GetValue()); - Azure::Storage::Blobs::ListBlobsSinglePageOptions options; - options.Prefix = blobName; do { auto res = m_blobContainerClient->ListBlobsSinglePage(options); @@ -330,10 +340,7 @@ namespace Azure { namespace Storage { namespace Test { getPropertiesResult = copyResult->PollUntilDone(std::chrono::seconds(1)); ASSERT_TRUE(getPropertiesResult->CopyStatus.HasValue()); EXPECT_EQ(getPropertiesResult->CopyStatus.GetValue(), Blobs::Models::CopyStatus::Success); - if (getPropertiesResult->IsSealed.HasValue()) - { - EXPECT_FALSE(getPropertiesResult->IsSealed.GetValue()); - } + EXPECT_FALSE(getPropertiesResult->IsSealed.GetValue()); copyOptions.ShouldSealDestination = true; copyResult = blobClient2.StartCopyFromUri(blobClient.GetUrl() + GetSas(), copyOptions); diff --git a/sdk/storage/azure-storage-blobs/test/block_blob_client_test.cpp b/sdk/storage/azure-storage-blobs/test/block_blob_client_test.cpp index 0e730558e..cc8115ef0 100644 --- a/sdk/storage/azure-storage-blobs/test/block_blob_client_test.cpp +++ b/sdk/storage/azure-storage-blobs/test/block_blob_client_test.cpp @@ -201,6 +201,22 @@ namespace Azure { namespace Storage { namespace Test { { EXPECT_TRUE(IsValidTime(properties.CopyCompletedOn.GetValue())); } + ASSERT_TRUE(properties.IsIncrementalCopy.HasValue()); + EXPECT_FALSE(properties.IsIncrementalCopy.GetValue()); + EXPECT_FALSE(properties.IncrementalCopyDestinationSnapshot.HasValue()); + + auto downloadResult = blobClient.Download(); + EXPECT_EQ(downloadResult->CopyId.GetValue(), res->CopyId); + EXPECT_FALSE(downloadResult->CopySource.GetValue().empty()); + EXPECT_TRUE( + downloadResult->CopyStatus.GetValue() == Azure::Storage::Blobs::Models::CopyStatus::Pending + || downloadResult->CopyStatus.GetValue() + == Azure::Storage::Blobs::Models::CopyStatus::Success); + EXPECT_FALSE(downloadResult->CopyProgress.GetValue().empty()); + if (downloadResult->CopyStatus.GetValue() == Azure::Storage::Blobs::Models::CopyStatus::Success) + { + EXPECT_TRUE(IsValidTime(downloadResult->CopyCompletedOn.GetValue())); + } } TEST_F(BlockBlobClientTest, SnapShotVersions) @@ -249,6 +265,72 @@ namespace Azure { namespace Storage { namespace Test { EXPECT_NO_THROW(m_blockBlobClient->GetProperties()); } + TEST_F(BlockBlobClientTest, IsCurrentVersion) + { + std::vector emptyContent; + std::string blobName = RandomString(); + auto blobClient = m_blobContainerClient->GetBlockBlobClient(blobName); + blobClient.UploadFrom(emptyContent.data(), emptyContent.size()); + + auto properties = *blobClient.GetProperties(); + ASSERT_TRUE(properties.VersionId.HasValue()); + ASSERT_TRUE(properties.IsCurrentVersion.HasValue()); + EXPECT_TRUE(properties.IsCurrentVersion.GetValue()); + + auto downloadResponse = blobClient.Download(); + ASSERT_TRUE(downloadResponse->VersionId.HasValue()); + ASSERT_TRUE(downloadResponse->IsCurrentVersion.HasValue()); + EXPECT_TRUE(downloadResponse->IsCurrentVersion.GetValue()); + + std::string version1 = properties.VersionId.GetValue(); + + blobClient.CreateSnapshot(); + + properties = *blobClient.GetProperties(); + ASSERT_TRUE(properties.VersionId.HasValue()); + ASSERT_TRUE(properties.IsCurrentVersion.HasValue()); + EXPECT_TRUE(properties.IsCurrentVersion.GetValue()); + std::string latestVersion = properties.VersionId.GetValue(); + EXPECT_NE(version1, properties.VersionId.GetValue()); + + auto versionClient = blobClient.WithVersionId(version1); + properties = *versionClient.GetProperties(); + ASSERT_TRUE(properties.VersionId.HasValue()); + ASSERT_TRUE(properties.IsCurrentVersion.HasValue()); + EXPECT_FALSE(properties.IsCurrentVersion.GetValue()); + EXPECT_EQ(version1, properties.VersionId.GetValue()); + downloadResponse = versionClient.Download(); + ASSERT_TRUE(downloadResponse->VersionId.HasValue()); + ASSERT_TRUE(downloadResponse->IsCurrentVersion.HasValue()); + EXPECT_FALSE(downloadResponse->IsCurrentVersion.GetValue()); + EXPECT_EQ(version1, downloadResponse->VersionId.GetValue()); + + Azure::Storage::Blobs::ListBlobsSinglePageOptions options; + options.Prefix = blobName; + options.Include = Blobs::Models::ListBlobsIncludeFlags::Versions; + do + { + auto res = m_blobContainerClient->ListBlobsSinglePage(options); + options.ContinuationToken = res->ContinuationToken; + for (const auto& blob : res->Items) + { + if (blob.Name == blobName) + { + ASSERT_TRUE(blob.VersionId.HasValue()); + ASSERT_TRUE(blob.IsCurrentVersion.HasValue()); + if (blob.VersionId.GetValue() == latestVersion) + { + EXPECT_TRUE(blob.IsCurrentVersion.GetValue()); + } + else + { + EXPECT_FALSE(blob.IsCurrentVersion.GetValue()); + } + } + } + } while (options.ContinuationToken.HasValue()); + } + TEST_F(BlockBlobClientTest, Properties) { auto blockBlobClient = Azure::Storage::Blobs::BlockBlobClient::CreateFromConnectionString( @@ -845,4 +927,62 @@ namespace Azure { namespace Storage { namespace Test { EXPECT_THROW(blobClient.WithSnapshot(s2).GetProperties(), StorageException); } + TEST_F(BlockBlobClientTest, SetTier) + { + std::vector emptyContent; + std::string blobName = RandomString(); + auto blobClient = m_blobContainerClient->GetBlockBlobClient(blobName); + blobClient.UploadFrom(emptyContent.data(), emptyContent.size()); + + auto properties = *blobClient.GetProperties(); + ASSERT_TRUE(properties.Tier.HasValue()); + ASSERT_TRUE(properties.IsAccessTierInferred.HasValue()); + EXPECT_TRUE(properties.IsAccessTierInferred.GetValue()); + EXPECT_FALSE(properties.AccessTierChangedOn.HasValue()); + + Azure::Storage::Blobs::ListBlobsSinglePageOptions options; + options.Prefix = blobName; + do + { + auto res = m_blobContainerClient->ListBlobsSinglePage(options); + options.ContinuationToken = res->ContinuationToken; + for (const auto& blob : res->Items) + { + if (blob.Name == blobName) + { + ASSERT_TRUE(blob.Tier.HasValue()); + ASSERT_TRUE(blob.IsAccessTierInferred.HasValue()); + EXPECT_TRUE(blob.IsAccessTierInferred.GetValue()); + } + } + } while (options.ContinuationToken.HasValue()); + + // choose a different tier + auto targetTier = properties.Tier.GetValue() == Blobs::Models::AccessTier::Hot + ? Blobs::Models::AccessTier::Cool + : Blobs::Models::AccessTier::Hot; + blobClient.SetAccessTier(targetTier); + + properties = *blobClient.GetProperties(); + ASSERT_TRUE(properties.Tier.HasValue()); + ASSERT_TRUE(properties.IsAccessTierInferred.HasValue()); + EXPECT_FALSE(properties.IsAccessTierInferred.GetValue()); + EXPECT_TRUE(properties.AccessTierChangedOn.HasValue()); + + do + { + auto res = m_blobContainerClient->ListBlobsSinglePage(options); + options.ContinuationToken = res->ContinuationToken; + for (const auto& blob : res->Items) + { + if (blob.Name == blobName) + { + ASSERT_TRUE(blob.Tier.HasValue()); + ASSERT_TRUE(blob.IsAccessTierInferred.HasValue()); + EXPECT_FALSE(blob.IsAccessTierInferred.GetValue()); + } + } + } while (options.ContinuationToken.HasValue()); + } + }}} // namespace Azure::Storage::Test diff --git a/sdk/storage/azure-storage-blobs/test/page_blob_client_test.cpp b/sdk/storage/azure-storage-blobs/test/page_blob_client_test.cpp index ff707ee03..eaa4ac337 100644 --- a/sdk/storage/azure-storage-blobs/test/page_blob_client_test.cpp +++ b/sdk/storage/azure-storage-blobs/test/page_blob_client_test.cpp @@ -160,6 +160,18 @@ namespace Azure { namespace Storage { namespace Test { auto getPropertiesResult = copyInfo->PollUntilDone(std::chrono::seconds(1)); ASSERT_TRUE(getPropertiesResult->CopyStatus.HasValue()); EXPECT_EQ(getPropertiesResult->CopyStatus.GetValue(), Blobs::Models::CopyStatus::Success); + ASSERT_TRUE(getPropertiesResult->CopyId.HasValue()); + EXPECT_FALSE(getPropertiesResult->CopyId.GetValue().empty()); + ASSERT_TRUE(getPropertiesResult->CopySource.HasValue()); + EXPECT_FALSE(getPropertiesResult->CopySource.GetValue().empty()); + ASSERT_TRUE(getPropertiesResult->IsIncrementalCopy.HasValue()); + EXPECT_TRUE(getPropertiesResult->IsIncrementalCopy.GetValue()); + ASSERT_TRUE(getPropertiesResult->IncrementalCopyDestinationSnapshot.HasValue()); + EXPECT_FALSE(getPropertiesResult->IncrementalCopyDestinationSnapshot.GetValue().empty()); + ASSERT_TRUE(getPropertiesResult->CopyCompletedOn.HasValue()); + EXPECT_TRUE(IsValidTime(getPropertiesResult->CopyCompletedOn.GetValue())); + ASSERT_TRUE(getPropertiesResult->CopyProgress.HasValue()); + EXPECT_FALSE(getPropertiesResult->CopyProgress.GetValue().empty()); } TEST_F(PageBlobClientTest, Lease)