From 68643e3ee22a8674efc1c779b17f96f934972313 Mon Sep 17 00:00:00 2001 From: JinmingHu Date: Tue, 11 Aug 2020 10:40:59 +0800 Subject: [PATCH] [Storage Blobs Service] Add test cases for LastModifiedTime and ETag and lease id (#425) * add test cases for LastModifiedTime and ETag and lease id --- sdk/storage/src/blobs/blob_client.cpp | 2 +- .../src/blobs/blob_container_client.cpp | 6 + .../test/blobs/append_blob_client_test.cpp | 165 ++++++++++++++++++ .../test/blobs/blob_container_client_test.cpp | 69 ++++++++ sdk/storage/test/test_base.cpp | 26 ++- sdk/storage/test/test_base.hpp | 8 +- 6 files changed, 267 insertions(+), 9 deletions(-) diff --git a/sdk/storage/src/blobs/blob_client.cpp b/sdk/storage/src/blobs/blob_client.cpp index ad87d62f8..cbecd4972 100644 --- a/sdk/storage/src/blobs/blob_client.cpp +++ b/sdk/storage/src/blobs/blob_client.cpp @@ -555,7 +555,7 @@ namespace Azure { namespace Storage { namespace Blobs { protocolLayerOptions.IfNoneMatch = options.AccessConditions.IfNoneMatch; protocolLayerOptions.SourceLeaseId = options.SourceConditions.LeaseId; protocolLayerOptions.SourceIfModifiedSince = options.SourceConditions.IfModifiedSince; - protocolLayerOptions.SourceIfUnmodifiedSince = options.AccessConditions.IfUnmodifiedSince; + protocolLayerOptions.SourceIfUnmodifiedSince = options.SourceConditions.IfUnmodifiedSince; protocolLayerOptions.SourceIfMatch = options.SourceConditions.IfMatch; protocolLayerOptions.SourceIfNoneMatch = options.SourceConditions.IfNoneMatch; return BlobRestClient::Blob::StartCopyFromUri( diff --git a/sdk/storage/src/blobs/blob_container_client.cpp b/sdk/storage/src/blobs/blob_container_client.cpp index 6fa7e30b1..56fc49c1c 100644 --- a/sdk/storage/src/blobs/blob_container_client.cpp +++ b/sdk/storage/src/blobs/blob_container_client.cpp @@ -177,6 +177,12 @@ namespace Azure { namespace Storage { namespace Blobs { protocolLayerOptions.Metadata = metadata; protocolLayerOptions.LeaseId = options.AccessConditions.LeaseId; protocolLayerOptions.IfModifiedSince = options.AccessConditions.IfModifiedSince; + if (options.AccessConditions.IfUnmodifiedSince.HasValue()) + { + // Strangely enough, this operation doesn't support If-Unmodified-Since while it does support + // If-Modified-Since + throw std::runtime_error("this operation doesn't support unmodified since access condition."); + } return BlobRestClient::Container::SetMetadata( options.Context, *m_pipeline, m_containerUrl.ToString(), protocolLayerOptions); } diff --git a/sdk/storage/test/blobs/append_blob_client_test.cpp b/sdk/storage/test/blobs/append_blob_client_test.cpp index ba7d483f8..0c6a756fe 100644 --- a/sdk/storage/test/blobs/append_blob_client_test.cpp +++ b/sdk/storage/test/blobs/append_blob_client_test.cpp @@ -90,4 +90,169 @@ namespace Azure { namespace Storage { namespace Test { EXPECT_THROW(appendBlobClient.Delete(), StorageError); } + TEST_F(AppendBlobClientTest, AccessConditionLastModifiedTime) + { + auto appendBlobClient = Azure::Storage::Blobs::AppendBlobClient::CreateFromConnectionString( + StandardStorageConnectionString(), m_containerName, RandomString()); + appendBlobClient.Create(); + + enum class TimePoint + { + TimeBefore, + TimeAfter, + None, + }; + + enum class Condition + { + ModifiedSince, + UnmodifiedSince, + }; + + auto lastModifiedTime = FromRfc1123(appendBlobClient.GetProperties()->LastModified); + auto timeBeforeStr = ToRfc1123(lastModifiedTime - std::chrono::seconds(1)); + auto timeAfterStr = ToRfc1123(lastModifiedTime + std::chrono::seconds(1)); + for (auto condition : {Condition::ModifiedSince, Condition::UnmodifiedSince}) + { + for (auto sinceTime : {TimePoint::TimeBefore, TimePoint::TimeAfter}) + { + Blobs::GetBlobPropertiesOptions options; + if (condition == Condition::ModifiedSince) + { + options.AccessConditions.IfModifiedSince + = sinceTime == TimePoint::TimeBefore ? timeBeforeStr : timeAfterStr; + } + else if (condition == Condition::UnmodifiedSince) + { + options.AccessConditions.IfUnmodifiedSince + = sinceTime == TimePoint::TimeBefore ? timeBeforeStr : timeAfterStr; + } + bool shouldThrow + = (condition == Condition::ModifiedSince && sinceTime == TimePoint::TimeAfter) + || (condition == Condition::UnmodifiedSince && sinceTime == TimePoint::TimeBefore); + if (shouldThrow) + { + EXPECT_THROW(appendBlobClient.GetProperties(options), StorageError); + } + else + { + EXPECT_NO_THROW(appendBlobClient.GetProperties(options)); + } + } + } + } + + TEST_F(AppendBlobClientTest, AccessConditionETag) + { + auto appendBlobClient = Azure::Storage::Blobs::AppendBlobClient::CreateFromConnectionString( + StandardStorageConnectionString(), m_containerName, RandomString()); + + Blobs::CreateAppendBlobOptions createOptions; + createOptions.AccessConditions.IfNoneMatch = "*"; + EXPECT_NO_THROW(appendBlobClient.Create(createOptions)); + EXPECT_THROW(appendBlobClient.Create(createOptions), StorageError); + + std::string eTag = appendBlobClient.GetProperties()->ETag; + for (std::string match : {eTag, std::string(c_dummyETag), std::string()}) + { + for (std::string noneMatch : {eTag, std::string(c_dummyETag), std::string()}) + { + Blobs::GetBlobPropertiesOptions options; + if (!match.empty()) + { + options.AccessConditions.IfMatch = match; + } + if (!noneMatch.empty()) + { + options.AccessConditions.IfNoneMatch = noneMatch; + } + bool shouldThrow = (!match.empty() && match != eTag) || noneMatch == eTag; + if (shouldThrow) + { + EXPECT_THROW(appendBlobClient.GetProperties(options), StorageError); + } + else + { + EXPECT_NO_THROW(appendBlobClient.GetProperties(options)); + } + } + } + } + + TEST_F(AppendBlobClientTest, AccessConditionLeaseId) + { + auto appendBlobClient = Azure::Storage::Blobs::AppendBlobClient::CreateFromConnectionString( + StandardStorageConnectionString(), m_containerName, RandomString()); + appendBlobClient.Create(); + + std::string leaseId = CreateUniqueLeaseId(); + appendBlobClient.AcquireLease(leaseId, 30); + EXPECT_THROW(appendBlobClient.Delete(), StorageError); + Blobs::DeleteBlobOptions options; + options.AccessConditions.LeaseId = leaseId; + EXPECT_NO_THROW(appendBlobClient.Delete(options)); + } + + TEST_F(AppendBlobClientTest, SourceBlobAccessConditions) + { + auto sourceBlobClient = Azure::Storage::Blobs::AppendBlobClient::CreateFromConnectionString( + StandardStorageConnectionString(), m_containerName, RandomString()); + sourceBlobClient.Create(); + auto leaseResponse + = sourceBlobClient.AcquireLease(CreateUniqueLeaseId(), c_InfiniteLeaseDuration); + std::string leaseId = leaseResponse->LeaseId; + std::string eTag = leaseResponse->ETag; + auto lastModifiedTime = FromRfc1123(leaseResponse->LastModified); + auto timeBeforeStr = ToRfc1123(lastModifiedTime - std::chrono::seconds(1)); + auto timeAfterStr = ToRfc1123(lastModifiedTime + std::chrono::seconds(1)); + + auto destBlobClient = Azure::Storage::Blobs::AppendBlobClient::CreateFromConnectionString( + StandardStorageConnectionString(), m_containerName, RandomString()); + + { + Blobs::StartCopyFromUriOptions options; + options.SourceConditions.LeaseId = CreateUniqueLeaseId(); + /* + don't know why, the copy operation also succeeds even if the lease id doesn't match. + EXPECT_THROW( + destBlobClient.StartCopyFromUri(sourceBlobClient.GetUri(), options), StorageError); + */ + options.SourceConditions.LeaseId = leaseId; + EXPECT_NO_THROW(destBlobClient.StartCopyFromUri(sourceBlobClient.GetUri(), options)); + } + sourceBlobClient.BreakLease(); + { + Blobs::StartCopyFromUriOptions options; + options.SourceConditions.IfMatch = eTag; + EXPECT_NO_THROW(destBlobClient.StartCopyFromUri(sourceBlobClient.GetUri(), options)); + options.SourceConditions.IfMatch = c_dummyETag; + EXPECT_THROW( + destBlobClient.StartCopyFromUri(sourceBlobClient.GetUri(), options), StorageError); + } + { + Blobs::StartCopyFromUriOptions options; + options.SourceConditions.IfNoneMatch = c_dummyETag; + EXPECT_NO_THROW(destBlobClient.StartCopyFromUri(sourceBlobClient.GetUri(), options)); + options.SourceConditions.IfNoneMatch = eTag; + EXPECT_THROW( + destBlobClient.StartCopyFromUri(sourceBlobClient.GetUri(), options), StorageError); + } + { + Blobs::StartCopyFromUriOptions options; + options.SourceConditions.IfModifiedSince = timeBeforeStr; + EXPECT_NO_THROW(destBlobClient.StartCopyFromUri(sourceBlobClient.GetUri(), options)); + options.SourceConditions.IfModifiedSince = timeAfterStr; + EXPECT_THROW( + destBlobClient.StartCopyFromUri(sourceBlobClient.GetUri(), options), StorageError); + } + { + Blobs::StartCopyFromUriOptions options; + options.SourceConditions.IfUnmodifiedSince = timeAfterStr; + EXPECT_NO_THROW(destBlobClient.StartCopyFromUri(sourceBlobClient.GetUri(), options)); + options.SourceConditions.IfUnmodifiedSince = timeBeforeStr; + EXPECT_THROW( + destBlobClient.StartCopyFromUri(sourceBlobClient.GetUri(), options), StorageError); + } + } + }}} // namespace Azure::Storage::Test diff --git a/sdk/storage/test/blobs/blob_container_client_test.cpp b/sdk/storage/test/blobs/blob_container_client_test.cpp index 11fc880ee..2999791fa 100644 --- a/sdk/storage/test/blobs/blob_container_client_test.cpp +++ b/sdk/storage/test/blobs/blob_container_client_test.cpp @@ -553,4 +553,73 @@ namespace Azure { namespace Storage { namespace Test { } } + TEST_F(BlobContainerClientTest, AccessConditionLastModifiedTime) + { + auto containerClient = Azure::Storage::Blobs::BlobContainerClient::CreateFromConnectionString( + StandardStorageConnectionString(), LowercaseRandomString()); + containerClient.Create(); + + enum class TimePoint + { + TimeBefore, + TimeAfter, + None, + }; + + enum class Condition + { + ModifiedSince, + UnmodifiedSince, + }; + + for (auto condition : {Condition::ModifiedSince, Condition::UnmodifiedSince}) + { + for (auto sinceTime : {TimePoint::TimeBefore, TimePoint::TimeAfter}) + { + auto lastModifiedTime = FromRfc1123(containerClient.GetProperties()->LastModified); + auto timeBeforeStr = ToRfc1123(lastModifiedTime - std::chrono::seconds(1)); + auto timeAfterStr = ToRfc1123(lastModifiedTime + std::chrono::seconds(1)); + + Blobs::SetBlobContainerAccessPolicyOptions options; + options.AccessType = Blobs::PublicAccessType::Private; + if (condition == Condition::ModifiedSince) + { + options.AccessConditions.IfModifiedSince + = sinceTime == TimePoint::TimeBefore ? timeBeforeStr : timeAfterStr; + } + else if (condition == Condition::UnmodifiedSince) + { + options.AccessConditions.IfUnmodifiedSince + = sinceTime == TimePoint::TimeBefore ? timeBeforeStr : timeAfterStr; + } + bool shouldThrow + = (condition == Condition::ModifiedSince && sinceTime == TimePoint::TimeAfter) + || (condition == Condition::UnmodifiedSince && sinceTime == TimePoint::TimeBefore); + if (shouldThrow) + { + EXPECT_THROW(containerClient.SetAccessPolicy(options), StorageError); + } + else + { + EXPECT_NO_THROW(containerClient.SetAccessPolicy(options)); + } + } + } + containerClient.Delete(); + } + + TEST_F(BlobContainerClientTest, AccessConditionLeaseId) + { + auto containerClient = Azure::Storage::Blobs::BlobContainerClient::CreateFromConnectionString( + StandardStorageConnectionString(), LowercaseRandomString()); + containerClient.Create(); + + std::string leaseId = CreateUniqueLeaseId(); + containerClient.AcquireLease(leaseId, 30); + EXPECT_THROW(containerClient.Delete(), StorageError); + Blobs::DeleteBlobContainerOptions options; + options.AccessConditions.LeaseId = leaseId; + EXPECT_NO_THROW(containerClient.Delete(options)); + } + }}} // namespace Azure::Storage::Test diff --git a/sdk/storage/test/test_base.cpp b/sdk/storage/test/test_base.cpp index 78d4b2a68..46c2c6b73 100644 --- a/sdk/storage/test/test_base.cpp +++ b/sdk/storage/test/test_base.cpp @@ -218,10 +218,10 @@ namespace Azure { namespace Storage { namespace Test { } std::string ToIso8601( - const std::chrono::system_clock::time_point& time_point, + const std::chrono::system_clock::time_point& timePoint, int numDecimalDigits) { - std::time_t epoch_seconds = std::chrono::system_clock::to_time_t(time_point); + std::time_t epoch_seconds = std::chrono::system_clock::to_time_t(timePoint); struct tm ct; #ifdef _WIN32 gmtime_s(&ct, &epoch_seconds); @@ -235,8 +235,8 @@ namespace Azure { namespace Storage { namespace Test { if (numDecimalDigits != 0) { time_str += "."; - auto time_point_second = std::chrono::time_point_cast(time_point); - auto decimal_part = time_point - time_point_second; + auto time_point_second = std::chrono::time_point_cast(timePoint); + auto decimal_part = timePoint - time_point_second; uint64_t num_nanoseconds = std::chrono::duration_cast(decimal_part).count(); std::string decimal_part_str = std::to_string(num_nanoseconds); @@ -248,9 +248,9 @@ namespace Azure { namespace Storage { namespace Test { return time_str; } - std::string ToRfc1123(const std::chrono::system_clock::time_point& time_point) + std::string ToRfc1123(const std::chrono::system_clock::time_point& timePoint) { - std::time_t epoch_seconds = std::chrono::system_clock::to_time_t(time_point); + std::time_t epoch_seconds = std::chrono::system_clock::to_time_t(timePoint); struct tm ct; #ifdef _WIN32 gmtime_s(&ct, &epoch_seconds); @@ -263,4 +263,18 @@ namespace Azure { namespace Storage { namespace Test { return ss.str(); } + std::chrono::system_clock::time_point FromRfc1123(const std::string& timeStr) + { + std::tm t; + std::stringstream ss(timeStr); + ss.imbue(std::locale("C")); + ss >> std::get_time(&t, "%a, %d %b %Y %H:%M:%S GMT"); +#ifdef _WIN32 + time_t tt = _mkgmtime(&t); +#else + time_t tt = timegm(&t); +#endif + return std::chrono::system_clock::from_time_t(tt); + } + }}} // namespace Azure::Storage::Test diff --git a/sdk/storage/test/test_base.hpp b/sdk/storage/test/test_base.hpp index 1a2336ad0..2018f8067 100644 --- a/sdk/storage/test/test_base.hpp +++ b/sdk/storage/test/test_base.hpp @@ -38,6 +38,8 @@ namespace Azure { namespace Storage { namespace Test { return x * 1024 * 1024 * 1024 * 1024; } + constexpr static const char* c_dummyETag = "0x8D83B58BDF51D75"; + std::string RandomString(size_t size = 10); std::string LowercaseRandomString(size_t size = 10); @@ -64,8 +66,10 @@ namespace Azure { namespace Storage { namespace Test { void DeleteFile(const std::string& filename); std::string ToIso8601( - const std::chrono::system_clock::time_point& time_point, + const std::chrono::system_clock::time_point& timePoint, int numDecimalDigits = 0); - std::string ToRfc1123(const std::chrono::system_clock::time_point& time_point); + std::string ToRfc1123(const std::chrono::system_clock::time_point& timePoint); + + std::chrono::system_clock::time_point FromRfc1123(const std::string& timeStr); }}} // namespace Azure::Storage::Test