[Storage Blobs Service] Undelete Container (#462)

* Undelete Container
This commit is contained in:
JinmingHu 2020-08-18 09:11:59 +08:00 committed by GitHub
parent 9067d8ac5d
commit 1dce74c642
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 268 additions and 30 deletions

View File

@ -148,6 +148,19 @@ namespace Azure { namespace Storage { namespace Blobs {
Azure::Core::Response<DeleteContainerResult> Delete(
const DeleteContainerOptions& options = DeleteContainerOptions()) const;
/**
* @brief Restores a previously deleted container.
*
* @param deletedContainerName The name of the previously deleted container.
* @param deletedContainerVersion The version of the previously deleted container.
* @param options Optional parameters to execute this function.
* @return An UndeleteContainerResult if successful.
*/
Azure::Core::Response<UndeleteContainerResult> UndeleteContainer(
const std::string& deletedContainerName,
const std::string& deletedContainerVersion,
const UndeleteContainerOptions& options = UndeleteContainerOptions()) const;
/**
* @brief Returns all user-defined metadata and system properties for the specified
* container. The data returned does not include the container's list of blobs.

View File

@ -123,7 +123,7 @@ namespace Azure { namespace Storage { namespace Blobs {
/**
* @brief Specifies that the container's metadata be returned.
*/
ListBlobContainersIncludeOption Include = ListBlobContainersIncludeOption::None;
ListBlobContainersIncludeItem Include = ListBlobContainersIncludeItem::None;
};
/**
@ -280,6 +280,17 @@ namespace Azure { namespace Storage { namespace Blobs {
ContainerAccessConditions AccessConditions;
};
/**
* @brief Optional parameters for BlobContainerClient::Undelete.
*/
struct UndeleteContainerOptions
{
/**
* @brief Context for cancelling long running operations.
*/
Azure::Core::Context Context;
};
/**
* @brief Optional parameters for BlobContainerClient::GetProperties.
*/

View File

@ -864,57 +864,59 @@ namespace Azure { namespace Storage { namespace Blobs {
std::string Value;
}; // struct GetUserDelegationKeyResult
enum class ListBlobContainersIncludeOption
enum class ListBlobContainersIncludeItem
{
None = 0,
Metadata = 1,
}; // bitwise enum ListBlobContainersIncludeOption
Deleted = 2,
}; // bitwise enum ListBlobContainersIncludeItem
inline ListBlobContainersIncludeOption operator|(
ListBlobContainersIncludeOption lhs,
ListBlobContainersIncludeOption rhs)
inline ListBlobContainersIncludeItem operator|(
ListBlobContainersIncludeItem lhs,
ListBlobContainersIncludeItem rhs)
{
using type = std::underlying_type_t<ListBlobContainersIncludeOption>;
return static_cast<ListBlobContainersIncludeOption>(
using type = std::underlying_type_t<ListBlobContainersIncludeItem>;
return static_cast<ListBlobContainersIncludeItem>(
static_cast<type>(lhs) | static_cast<type>(rhs));
}
inline ListBlobContainersIncludeOption& operator|=(
ListBlobContainersIncludeOption& lhs,
ListBlobContainersIncludeOption rhs)
inline ListBlobContainersIncludeItem& operator|=(
ListBlobContainersIncludeItem& lhs,
ListBlobContainersIncludeItem rhs)
{
lhs = lhs | rhs;
return lhs;
}
inline ListBlobContainersIncludeOption operator&(
ListBlobContainersIncludeOption lhs,
ListBlobContainersIncludeOption rhs)
inline ListBlobContainersIncludeItem operator&(
ListBlobContainersIncludeItem lhs,
ListBlobContainersIncludeItem rhs)
{
using type = std::underlying_type_t<ListBlobContainersIncludeOption>;
return static_cast<ListBlobContainersIncludeOption>(
using type = std::underlying_type_t<ListBlobContainersIncludeItem>;
return static_cast<ListBlobContainersIncludeItem>(
static_cast<type>(lhs) & static_cast<type>(rhs));
}
inline ListBlobContainersIncludeOption& operator&=(
ListBlobContainersIncludeOption& lhs,
ListBlobContainersIncludeOption rhs)
inline ListBlobContainersIncludeItem& operator&=(
ListBlobContainersIncludeItem& lhs,
ListBlobContainersIncludeItem rhs)
{
lhs = lhs & rhs;
return lhs;
}
inline std::string ListBlobContainersIncludeOptionToString(
const ListBlobContainersIncludeOption& val)
inline std::string ListBlobContainersIncludeItemToString(const ListBlobContainersIncludeItem& val)
{
ListBlobContainersIncludeOption value_list[] = {
ListBlobContainersIncludeOption::Metadata,
ListBlobContainersIncludeItem value_list[] = {
ListBlobContainersIncludeItem::Metadata,
ListBlobContainersIncludeItem::Deleted,
};
const char* string_list[] = {
"metadata",
"deleted",
};
std::string ret;
for (std::size_t i = 0; i < sizeof(value_list) / sizeof(ListBlobContainersIncludeOption); ++i)
for (std::size_t i = 0; i < sizeof(value_list) / sizeof(ListBlobContainersIncludeItem); ++i)
{
if ((val & value_list[i]) == value_list[i])
{
@ -1245,6 +1247,10 @@ namespace Azure { namespace Storage { namespace Blobs {
{
}; // struct UndeleteBlobResult
struct UndeleteContainerResult
{
}; // struct UndeleteContainerResult
struct UploadBlockBlobResult
{
std::string ETag;
@ -1303,6 +1309,12 @@ namespace Azure { namespace Storage { namespace Blobs {
Azure::Core::Nullable<std::string> LeaseDuration;
BlobLeaseState LeaseState = BlobLeaseState::Available;
BlobLeaseStatus LeaseStatus = BlobLeaseStatus::Unlocked;
std::string DefaultEncryptionScope;
bool PreventEncryptionScopeOverride = false;
bool IsDeleted = false;
Azure::Core::Nullable<std::string> VersionId;
Azure::Core::Nullable<std::string> DeletedOn;
Azure::Core::Nullable<int32_t> RemainingRetentionDays;
}; // struct BlobContainerItem
struct BlobGeoReplication
@ -1427,6 +1439,8 @@ namespace Azure { namespace Storage { namespace Blobs {
Azure::Core::Nullable<std::string> LeaseDuration;
BlobLeaseState LeaseState = BlobLeaseState::Available;
BlobLeaseStatus LeaseStatus = BlobLeaseStatus::Unlocked;
std::string DefaultEncryptionScope;
bool PreventEncryptionScopeOverride = false;
}; // struct GetContainerPropertiesResult
struct StartCopyBlobFromUriResult
@ -1515,7 +1529,7 @@ namespace Azure { namespace Storage { namespace Blobs {
Azure::Core::Nullable<std::string> Prefix;
Azure::Core::Nullable<std::string> Marker;
Azure::Core::Nullable<int32_t> MaxResults;
ListBlobContainersIncludeOption IncludeMetadata = ListBlobContainersIncludeOption::None;
ListBlobContainersIncludeItem Include = ListBlobContainersIncludeItem::None;
}; // struct ListContainersSegmentOptions
static Azure::Core::Response<ListContainersSegmentResult> ListBlobContainers(
@ -1544,11 +1558,11 @@ namespace Azure { namespace Storage { namespace Blobs {
{
request.AddQueryParameter("maxresults", std::to_string(options.MaxResults.GetValue()));
}
std::string list_blob_containers_include_option
= ListBlobContainersIncludeOptionToString(options.IncludeMetadata);
if (!list_blob_containers_include_option.empty())
std::string list_blob_containers_include_item
= ListBlobContainersIncludeItemToString(options.Include);
if (!list_blob_containers_include_item.empty())
{
request.AddQueryParameter("include", list_blob_containers_include_option);
request.AddQueryParameter("include", list_blob_containers_include_item);
}
auto pHttpResponse = pipeline.Send(context, request);
Azure::Core::Http::RawResponse& httpResponse = *pHttpResponse;
@ -2297,7 +2311,13 @@ namespace Azure { namespace Storage { namespace Blobs {
k_LeaseStatus,
k_LeaseState,
k_LeaseDuration,
k_DefaultEncryptionScope,
k_DenyEncryptionScopeOverride,
k_Metadata,
k_Deleted,
k_Version,
k_DeletedTime,
k_RemainingRetentionDays,
k_Unknown,
};
std::vector<XmlTagName> path;
@ -2361,10 +2381,34 @@ namespace Azure { namespace Storage { namespace Blobs {
{
path.emplace_back(XmlTagName::k_LeaseDuration);
}
else if (std::strcmp(node.Name, "DefaultEncryptionScope") == 0)
{
path.emplace_back(XmlTagName::k_DefaultEncryptionScope);
}
else if (std::strcmp(node.Name, "DenyEncryptionScopeOverride") == 0)
{
path.emplace_back(XmlTagName::k_DenyEncryptionScopeOverride);
}
else if (std::strcmp(node.Name, "Metadata") == 0)
{
path.emplace_back(XmlTagName::k_Metadata);
}
else if (std::strcmp(node.Name, "Deleted") == 0)
{
path.emplace_back(XmlTagName::k_Deleted);
}
else if (std::strcmp(node.Name, "Version") == 0)
{
path.emplace_back(XmlTagName::k_Version);
}
else if (std::strcmp(node.Name, "DeletedTime") == 0)
{
path.emplace_back(XmlTagName::k_DeletedTime);
}
else if (std::strcmp(node.Name, "RemainingRetentionDays") == 0)
{
path.emplace_back(XmlTagName::k_RemainingRetentionDays);
}
else
{
path.emplace_back(XmlTagName::k_Unknown);
@ -2429,6 +2473,38 @@ namespace Azure { namespace Storage { namespace Blobs {
{
ret.LeaseDuration = node.Value;
}
else if (
path.size() == 2 && path[0] == XmlTagName::k_Properties
&& path[1] == XmlTagName::k_DefaultEncryptionScope)
{
ret.DefaultEncryptionScope = node.Value;
}
else if (
path.size() == 2 && path[0] == XmlTagName::k_Properties
&& path[1] == XmlTagName::k_DenyEncryptionScopeOverride)
{
ret.PreventEncryptionScopeOverride = std::strcmp(node.Value, "true") == 0;
}
else if (path.size() == 1 && path[0] == XmlTagName::k_Deleted)
{
ret.IsDeleted = std::strcmp(node.Value, "true") == 0;
}
else if (path.size() == 1 && path[0] == XmlTagName::k_Version)
{
ret.VersionId = node.Value;
}
else if (
path.size() == 2 && path[0] == XmlTagName::k_Properties
&& path[1] == XmlTagName::k_DeletedTime)
{
ret.DeletedOn = node.Value;
}
else if (
path.size() == 2 && path[0] == XmlTagName::k_Properties
&& path[1] == XmlTagName::k_RemainingRetentionDays)
{
ret.RemainingRetentionDays = std::stoi(node.Value);
}
}
}
return ret;
@ -3100,6 +3176,45 @@ namespace Azure { namespace Storage { namespace Blobs {
std::move(response), std::move(pHttpResponse));
}
struct UndeleteContainerOptions
{
Azure::Core::Nullable<int32_t> Timeout;
std::string DeletedContainerName;
std::string DeletedContainerVersion;
}; // struct UndeleteContainerOptions
static Azure::Core::Response<UndeleteContainerResult> Undelete(
const Azure::Core::Context& context,
Azure::Core::Http::HttpPipeline& pipeline,
const std::string& url,
const UndeleteContainerOptions& options)
{
unused(options);
auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url);
request.AddHeader("Content-Length", "0");
request.AddQueryParameter("restype", "container");
request.AddQueryParameter("comp", "undelete");
request.AddHeader("x-ms-version", c_ApiVersion);
if (options.Timeout.HasValue())
{
request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue()));
}
request.AddHeader("x-ms-deleted-container-name", options.DeletedContainerName);
request.AddHeader("x-ms-deleted-container-version", options.DeletedContainerVersion);
auto pHttpResponse = pipeline.Send(context, request);
Azure::Core::Http::RawResponse& httpResponse = *pHttpResponse;
UndeleteContainerResult response;
auto http_status_code
= static_cast<std::underlying_type<Azure::Core::Http::HttpStatusCode>::type>(
httpResponse.GetStatusCode());
if (!(http_status_code == 201))
{
throw StorageError::CreateFromResponse(context, std::move(pHttpResponse));
}
return Azure::Core::Response<UndeleteContainerResult>(
std::move(response), std::move(pHttpResponse));
}
struct GetContainerPropertiesOptions
{
Azure::Core::Nullable<int32_t> Timeout;
@ -3161,6 +3276,10 @@ namespace Azure { namespace Storage { namespace Blobs {
{
response.LeaseDuration = response_lease_duration_iterator->second;
}
response.DefaultEncryptionScope
= httpResponse.GetHeaders().at("x-ms-default-encryption-scope");
response.PreventEncryptionScopeOverride
= httpResponse.GetHeaders().at("x-ms-deny-encryption-scope-override") == "true";
return Azure::Core::Response<GetContainerPropertiesResult>(
std::move(response), std::move(pHttpResponse));
}

View File

@ -11,6 +11,7 @@ namespace Azure { namespace Storage {
template <class... T> void unused(T&&...) {}
constexpr int32_t c_InfiniteLeaseDuration = -1;
constexpr static const char* c_AccountEncryptionKey = "$account-encryption-key";
std::string CreateUniqueLeaseId();

View File

@ -163,6 +163,18 @@ namespace Azure { namespace Storage { namespace Blobs {
options.Context, *m_pipeline, m_containerUrl.ToString(), protocolLayerOptions);
}
Azure::Core::Response<UndeleteContainerResult> BlobContainerClient::UndeleteContainer(
const std::string& deletedContainerName,
const std::string& deletedContainerVersion,
const UndeleteContainerOptions& options) const
{
BlobRestClient::Container::UndeleteContainerOptions protocolLayerOptions;
protocolLayerOptions.DeletedContainerName = deletedContainerName;
protocolLayerOptions.DeletedContainerVersion = deletedContainerVersion;
return BlobRestClient::Container::Undelete(
options.Context, *m_pipeline, m_containerUrl.ToString(), protocolLayerOptions);
}
Azure::Core::Response<GetContainerPropertiesResult> BlobContainerClient::GetProperties(
const GetContainerPropertiesOptions& options) const
{

View File

@ -127,7 +127,7 @@ namespace Azure { namespace Storage { namespace Blobs {
protocolLayerOptions.Prefix = options.Prefix;
protocolLayerOptions.Marker = options.Marker;
protocolLayerOptions.MaxResults = options.MaxResults;
protocolLayerOptions.IncludeMetadata = options.Include;
protocolLayerOptions.Include = options.Include;
return BlobRestClient::Service::ListBlobContainers(
options.Context, *m_pipeline, m_serviceUrl.ToString(), protocolLayerOptions);
}

View File

@ -5,6 +5,9 @@
#include "blobs/blob_sas_builder.hpp"
#include "common/crypt.hpp"
#include <chrono>
#include <thread>
namespace Azure { namespace Storage { namespace Blobs {
bool operator==(
@ -396,6 +399,11 @@ namespace Azure { namespace Storage { namespace Test {
TEST_F(BlobContainerClientTest, EncryptionScope)
{
{
auto properties = *m_blobContainerClient->GetProperties();
EXPECT_EQ(properties.DefaultEncryptionScope, c_AccountEncryptionKey);
EXPECT_EQ(properties.PreventEncryptionScopeOverride, false);
}
{
std::string containerName = LowercaseRandomString();
std::string blobName = RandomString();
@ -407,6 +415,11 @@ namespace Azure { namespace Storage { namespace Test {
createOptions.DefaultEncryptionScope = c_TestEncryptionScope;
createOptions.PreventEncryptionScopeOverride = true;
EXPECT_NO_THROW(containerClient.Create(createOptions));
auto properties = *containerClient.GetProperties();
EXPECT_EQ(properties.DefaultEncryptionScope, createOptions.DefaultEncryptionScope.GetValue());
EXPECT_EQ(
properties.PreventEncryptionScopeOverride,
createOptions.PreventEncryptionScopeOverride.GetValue());
auto appendBlobClient = containerClient.GetAppendBlobClient(blobName);
auto blobContentInfo = appendBlobClient.Create();
EXPECT_TRUE(blobContentInfo->EncryptionScope.HasValue());
@ -622,4 +635,67 @@ namespace Azure { namespace Storage { namespace Test {
EXPECT_NO_THROW(containerClient.Delete(options));
}
TEST_F(BlobContainerClientTest, DISABLED_Undelete)
{
auto serviceClient = Azure::Storage::Blobs::BlobServiceClient::CreateFromConnectionString(
StandardStorageConnectionString());
std::string containerName = LowercaseRandomString();
auto containerClient = serviceClient.GetBlobContainerClient(containerName);
containerClient.Create();
containerClient.Delete();
Blobs::BlobContainerItem deletedContainerItem;
{
Azure::Storage::Blobs::ListContainersSegmentOptions options;
options.Prefix = containerName;
options.Include = Blobs::ListBlobContainersIncludeItem::Deleted;
do
{
auto res = serviceClient.ListBlobContainersSegment(options);
options.Marker = res->NextMarker;
for (const auto& container : res->Items)
{
if (container.Name == containerName)
{
deletedContainerItem = container;
break;
}
}
} while (!options.Marker.GetValue().empty());
}
EXPECT_EQ(deletedContainerItem.Name, containerName);
EXPECT_TRUE(deletedContainerItem.IsDeleted);
EXPECT_TRUE(deletedContainerItem.VersionId.HasValue());
EXPECT_FALSE(deletedContainerItem.VersionId.GetValue().empty());
EXPECT_TRUE(deletedContainerItem.DeletedOn.HasValue());
EXPECT_FALSE(deletedContainerItem.DeletedOn.GetValue().empty());
EXPECT_TRUE(deletedContainerItem.RemainingRetentionDays.HasValue());
EXPECT_GE(deletedContainerItem.RemainingRetentionDays.GetValue(), 0);
std::string containerName2 = LowercaseRandomString();
auto containerClient2 = serviceClient.GetBlobContainerClient(containerName2);
for (int i = 0; i < 60; ++i)
{
try
{
containerClient2.UndeleteContainer(
deletedContainerItem.Name, deletedContainerItem.VersionId.GetValue());
break;
}
catch (StorageError& e)
{
if (e.StatusCode == Azure::Core::Http::HttpStatusCode::Conflict
&& e.ReasonPhrase == "The specified container is being deleted.")
{
std::this_thread::sleep_for(std::chrono::seconds(1));
}
else
{
throw;
}
}
}
EXPECT_NO_THROW(containerClient2.GetProperties());
}
}}} // namespace Azure::Storage::Test

View File

@ -150,6 +150,12 @@ namespace Azure { namespace Storage { namespace Test {
EXPECT_FALSE(container.Name.empty());
EXPECT_FALSE(container.ETag.empty());
EXPECT_FALSE(container.LastModified.empty());
EXPECT_FALSE(container.IsDeleted);
EXPECT_FALSE(container.VersionId.HasValue());
EXPECT_FALSE(container.DeletedOn.HasValue());
EXPECT_FALSE(container.RemainingRetentionDays.HasValue());
EXPECT_EQ(container.DefaultEncryptionScope, c_AccountEncryptionKey);
EXPECT_FALSE(container.PreventEncryptionScopeOverride);
listContainers.insert(container.Name);
}
} while (!options.Marker.GetValue().empty());