Remove blob batch (#1309)
This commit is contained in:
parent
967ccae1ff
commit
4527798bd1
@ -65,6 +65,7 @@
|
||||
- Continuation token of result types are changed to nullable.
|
||||
- Rename `Models::DeleteSnapshotsOption::Only` to `Models::DeleteSnapshotsOption::OnlySnapshots`.
|
||||
- Rename `SourceConditions` in API options to `SourceAccessConditions`.
|
||||
- Remove Blob Batch.
|
||||
|
||||
## 12.0.0-beta.5 (2020-11-13)
|
||||
|
||||
|
||||
@ -30,7 +30,6 @@ set(
|
||||
AZURE_STORAGE_BLOB_HEADER
|
||||
inc/azure/storage/blobs/protocol/blob_rest_client.hpp
|
||||
inc/azure/storage/blobs/append_blob_client.hpp
|
||||
inc/azure/storage/blobs/blob_batch_client.hpp
|
||||
inc/azure/storage/blobs/blob_client.hpp
|
||||
inc/azure/storage/blobs/blob_container_client.hpp
|
||||
inc/azure/storage/blobs/blob_options.hpp
|
||||
@ -46,7 +45,6 @@ set(
|
||||
set(
|
||||
AZURE_STORAGE_BLOB_SOURCE
|
||||
src/append_blob_client.cpp
|
||||
src/blob_batch_client.cpp
|
||||
src/blob_client.cpp
|
||||
src/blob_container_client.cpp
|
||||
src/blob_rest_client.cpp
|
||||
@ -84,7 +82,6 @@ if(BUILD_TESTING)
|
||||
PRIVATE
|
||||
test/append_blob_client_test.cpp
|
||||
test/append_blob_client_test.hpp
|
||||
test/blob_batch_client_test.cpp
|
||||
test/blob_container_client_test.cpp
|
||||
test/blob_container_client_test.hpp
|
||||
test/blob_sas_test.cpp
|
||||
|
||||
@ -4,7 +4,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "azure/storage/blobs/append_blob_client.hpp"
|
||||
#include "azure/storage/blobs/blob_batch_client.hpp"
|
||||
#include "azure/storage/blobs/blob_client.hpp"
|
||||
#include "azure/storage/blobs/blob_container_client.hpp"
|
||||
#include "azure/storage/blobs/blob_sas_builder.hpp"
|
||||
|
||||
@ -1,170 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "azure/storage/blobs/blob_service_client.hpp"
|
||||
|
||||
namespace Azure { namespace Storage { namespace Blobs {
|
||||
|
||||
/**
|
||||
* @brief A BlobBatch allows you to batch multiple Azure Storage operations in a single request
|
||||
* via BlobBatchClient.SubmitBatch.
|
||||
*/
|
||||
class BlobBatch {
|
||||
public:
|
||||
/**
|
||||
* @brief Marks the specified blob or snapshot for deletion.
|
||||
*
|
||||
* @param blobContainerName The name of the container containing the blob to delete.
|
||||
* @param blobName
|
||||
* The name of the blob to delete.
|
||||
* @param options Optional parameters to execute this
|
||||
* function.
|
||||
* @return An index of this operation result in
|
||||
* SubmitBlobBatchResult.DeleteBlobResults, after this batch is submitted via
|
||||
* BlobBatchClient.SubmitBatch.
|
||||
*/
|
||||
int32_t DeleteBlob(
|
||||
const std::string& blobContainerName,
|
||||
const std::string& blobName,
|
||||
const DeleteBlobOptions& options = DeleteBlobOptions());
|
||||
|
||||
/**
|
||||
* @brief Sets the tier on a blob.
|
||||
*
|
||||
* @param blobContainerName The name of the
|
||||
* container containing the blob to set the tier of.
|
||||
* @param blobName The name of the blob
|
||||
* to set the tier of.
|
||||
* @param tier Indicates the tier to be set on the blob.
|
||||
*
|
||||
* @param options Optional parameters to execute this function.
|
||||
* @return An index of this
|
||||
* operation result in SubmitBlobBatchResult.SetBlobAccessTierResults, after this batch is
|
||||
* submitted via BlobBatchClient.SubmitBatch.
|
||||
*/
|
||||
int32_t SetBlobAccessTier(
|
||||
const std::string& blobContainerName,
|
||||
const std::string& blobName,
|
||||
Models::AccessTier tier,
|
||||
const SetBlobAccessTierOptions& options = SetBlobAccessTierOptions());
|
||||
|
||||
private:
|
||||
friend class BlobBatchClient;
|
||||
|
||||
struct DeleteBlobSubRequest
|
||||
{
|
||||
std::string BlobContainerName;
|
||||
std::string BlobName;
|
||||
DeleteBlobOptions Options;
|
||||
};
|
||||
|
||||
struct SetBlobAccessTierSubRequest
|
||||
{
|
||||
std::string BlobContainerName;
|
||||
std::string BlobName;
|
||||
Models::AccessTier Tier;
|
||||
SetBlobAccessTierOptions Options;
|
||||
};
|
||||
|
||||
std::vector<DeleteBlobSubRequest> m_deleteBlobSubRequests;
|
||||
std::vector<SetBlobAccessTierSubRequest> m_setBlobAccessTierSubRequests;
|
||||
};
|
||||
|
||||
struct SubmitBlobBatchResult
|
||||
{
|
||||
std::vector<Azure::Core::Response<Models::DeleteBlobResult>> DeleteBlobResults;
|
||||
std::vector<Azure::Core::Response<Models::SetBlobAccessTierResult>> SetBlobAccessTierResults;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The BlobBatchClient allows you to batch multiple Azure Storage operations in a
|
||||
* single request.
|
||||
*/
|
||||
class BlobBatchClient {
|
||||
public:
|
||||
/**
|
||||
* @brief Initialize a new instance of BlobBatchClient.
|
||||
*
|
||||
* @param connectionString A connection string includes the authentication information required
|
||||
* for your application to access data in an Azure Storage account at runtime.
|
||||
* @param options Optional client options that define the transport pipeline policies for
|
||||
* authentication, retries, etc., that are applied to every request and subrequest.
|
||||
* @return A new BlobBatchClient instance.
|
||||
*/
|
||||
static BlobBatchClient CreateFromConnectionString(
|
||||
const std::string& connectionString,
|
||||
const BlobClientOptions& options = BlobClientOptions());
|
||||
|
||||
/**
|
||||
* @brief Initialize a new instance of BlobBatchClient.
|
||||
*
|
||||
* @param serviceUrl A url referencing the blob that includes the name of the account.
|
||||
* @param credential The shared key credential used to sign requests.
|
||||
* @param options Optional client options that define the transport pipeline policies for
|
||||
* authentication, retries, etc., that are applied to every request and subrequest.
|
||||
*/
|
||||
explicit BlobBatchClient(
|
||||
const std::string& serviceUrl,
|
||||
std::shared_ptr<StorageSharedKeyCredential> credential,
|
||||
const BlobClientOptions& options = BlobClientOptions());
|
||||
|
||||
/**
|
||||
* @brief Initialize a new instance of BlobBatchClient.
|
||||
*
|
||||
* @param serviceUrl A url referencing the blob that includes the name of the account.
|
||||
* @param credential The token credential used to sign requests.
|
||||
* @param options Optional client options that define the transport pipeline policies for
|
||||
* authentication, retries, etc., that are applied to every request and subrequest.
|
||||
*/
|
||||
explicit BlobBatchClient(
|
||||
const std::string& serviceUrl,
|
||||
std::shared_ptr<Core::TokenCredential> credential,
|
||||
const BlobClientOptions& options = BlobClientOptions());
|
||||
|
||||
/**
|
||||
* @brief Initialize a new instance of BlobBatchClient.
|
||||
*
|
||||
* @param serviceUrl A url referencing the blob that includes the name of the account, and
|
||||
* possibly also a SAS token.
|
||||
* @param options Optional client options that define the transport pipeline policies for
|
||||
* authentication, retries, etc., that are applied to every request and subrequest.
|
||||
*/
|
||||
explicit BlobBatchClient(
|
||||
const std::string& serviceUrl,
|
||||
const BlobClientOptions& options = BlobClientOptions());
|
||||
|
||||
/**
|
||||
* @brief Creates a new BlobBatch to collect sub-operations that can be submitted
|
||||
* together via SubmitBatch.
|
||||
*
|
||||
* @return A new instance of BlobBatch.
|
||||
*/
|
||||
static BlobBatch CreateBatch() { return BlobBatch(); }
|
||||
|
||||
/**
|
||||
* @brief Submit a BlobBatch of sub-operations.
|
||||
*
|
||||
* @param batch A BlobBatch
|
||||
* of sub-operations.
|
||||
* @param options Optional parameters to execute this function.
|
||||
*
|
||||
* @return A SubmitBlobBatchResult on successful submitting.
|
||||
*/
|
||||
Azure::Core::Response<SubmitBlobBatchResult> SubmitBatch(
|
||||
const BlobBatch& batch,
|
||||
const SubmitBlobBatchOptions& options = SubmitBlobBatchOptions()) const;
|
||||
|
||||
protected:
|
||||
Azure::Core::Http::Url m_serviceUrl;
|
||||
std::shared_ptr<Azure::Core::Http::HttpPipeline> m_pipeline;
|
||||
std::shared_ptr<Azure::Core::Http::HttpPipeline> m_subRequestPipeline;
|
||||
};
|
||||
|
||||
}}} // namespace Azure::Storage::Blobs
|
||||
@ -1,432 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "azure/storage/blobs/blob_batch_client.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#include <azure/core/http/policy.hpp>
|
||||
#include <azure/storage/common/constants.hpp>
|
||||
#include <azure/storage/common/shared_key_policy.hpp>
|
||||
#include <azure/storage/common/storage_per_retry_policy.hpp>
|
||||
|
||||
#include "azure/storage/blobs/version.hpp"
|
||||
|
||||
namespace Azure { namespace Storage { namespace Blobs {
|
||||
|
||||
namespace {
|
||||
class NoopTransportPolicy : public Core::Http::HttpPolicy {
|
||||
public:
|
||||
~NoopTransportPolicy() override {}
|
||||
|
||||
std::unique_ptr<HttpPolicy> Clone() const override
|
||||
{
|
||||
return std::make_unique<NoopTransportPolicy>(*this);
|
||||
}
|
||||
|
||||
std::unique_ptr<Core::Http::RawResponse> Send(
|
||||
Core::Context const& context,
|
||||
Core::Http::Request& request,
|
||||
Core::Http::NextHttpPolicy nextHttpPolicy) const override
|
||||
{
|
||||
unused(context, request, nextHttpPolicy);
|
||||
return std::unique_ptr<Core::Http::RawResponse>();
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
int32_t BlobBatch::DeleteBlob(
|
||||
const std::string& blobContainerName,
|
||||
const std::string& blobName,
|
||||
const DeleteBlobOptions& options)
|
||||
{
|
||||
DeleteBlobSubRequest operation;
|
||||
operation.BlobContainerName = blobContainerName;
|
||||
operation.BlobName = blobName;
|
||||
operation.Options = options;
|
||||
m_deleteBlobSubRequests.emplace_back(std::move(operation));
|
||||
return static_cast<int32_t>(m_deleteBlobSubRequests.size() - 1);
|
||||
}
|
||||
|
||||
int32_t BlobBatch::SetBlobAccessTier(
|
||||
const std::string& blobContainerName,
|
||||
const std::string& blobName,
|
||||
Models::AccessTier tier,
|
||||
const SetBlobAccessTierOptions& options)
|
||||
{
|
||||
SetBlobAccessTierSubRequest operation;
|
||||
operation.BlobContainerName = blobContainerName;
|
||||
operation.BlobName = blobName;
|
||||
operation.Options = options;
|
||||
operation.Tier = tier;
|
||||
m_setBlobAccessTierSubRequests.emplace_back(std::move(operation));
|
||||
return static_cast<int32_t>(m_setBlobAccessTierSubRequests.size() - 1);
|
||||
}
|
||||
|
||||
BlobBatchClient BlobBatchClient::CreateFromConnectionString(
|
||||
const std::string& connectionString,
|
||||
const BlobClientOptions& options)
|
||||
{
|
||||
auto parsedConnectionString = Storage::Details::ParseConnectionString(connectionString);
|
||||
auto serviceUrl = std::move(parsedConnectionString.BlobServiceUrl);
|
||||
|
||||
if (parsedConnectionString.KeyCredential)
|
||||
{
|
||||
return BlobBatchClient(
|
||||
serviceUrl.GetAbsoluteUrl(), parsedConnectionString.KeyCredential, options);
|
||||
}
|
||||
else
|
||||
{
|
||||
return BlobBatchClient(serviceUrl.GetAbsoluteUrl(), options);
|
||||
}
|
||||
}
|
||||
|
||||
BlobBatchClient::BlobBatchClient(
|
||||
const std::string& serviceUrl,
|
||||
std::shared_ptr<StorageSharedKeyCredential> credential,
|
||||
const BlobClientOptions& options)
|
||||
: m_serviceUrl(serviceUrl)
|
||||
{
|
||||
std::vector<std::unique_ptr<Azure::Core::Http::HttpPolicy>> policies;
|
||||
policies.emplace_back(std::make_unique<Azure::Core::Http::TelemetryPolicy>(
|
||||
Storage::Details::BlobServicePackageName, Details::Version::VersionString()));
|
||||
policies.emplace_back(std::make_unique<Azure::Core::Http::RequestIdPolicy>());
|
||||
for (const auto& p : options.PerOperationPolicies)
|
||||
{
|
||||
policies.emplace_back(p->Clone());
|
||||
}
|
||||
policies.emplace_back(
|
||||
std::make_unique<Storage::Details::StorageRetryPolicy>(options.RetryOptions));
|
||||
for (const auto& p : options.PerRetryPolicies)
|
||||
{
|
||||
policies.emplace_back(p->Clone());
|
||||
}
|
||||
policies.emplace_back(std::make_unique<Storage::Details::StoragePerRetryPolicy>());
|
||||
policies.emplace_back(std::make_unique<Storage::Details::SharedKeyPolicy>(credential));
|
||||
policies.emplace_back(
|
||||
std::make_unique<Azure::Core::Http::TransportPolicy>(options.TransportPolicyOptions));
|
||||
m_pipeline = std::make_shared<Azure::Core::Http::HttpPipeline>(policies);
|
||||
|
||||
policies.clear();
|
||||
for (const auto& p : options.PerOperationPolicies)
|
||||
{
|
||||
policies.emplace_back(p->Clone());
|
||||
}
|
||||
for (const auto& p : options.PerRetryPolicies)
|
||||
{
|
||||
policies.emplace_back(p->Clone());
|
||||
}
|
||||
policies.emplace_back(std::make_unique<Storage::Details::StoragePerRetryPolicy>());
|
||||
policies.emplace_back(std::make_unique<Storage::Details::SharedKeyPolicy>(credential));
|
||||
policies.emplace_back(std::make_unique<NoopTransportPolicy>());
|
||||
m_subRequestPipeline = std::make_shared<Azure::Core::Http::HttpPipeline>(policies);
|
||||
}
|
||||
|
||||
BlobBatchClient::BlobBatchClient(
|
||||
const std::string& serviceUrl,
|
||||
std::shared_ptr<Core::TokenCredential> credential,
|
||||
const BlobClientOptions& options)
|
||||
: m_serviceUrl(serviceUrl)
|
||||
{
|
||||
std::vector<std::unique_ptr<Azure::Core::Http::HttpPolicy>> policies;
|
||||
policies.emplace_back(std::make_unique<Azure::Core::Http::TelemetryPolicy>(
|
||||
Storage::Details::BlobServicePackageName, Details::Version::VersionString()));
|
||||
policies.emplace_back(std::make_unique<Azure::Core::Http::RequestIdPolicy>());
|
||||
for (const auto& p : options.PerOperationPolicies)
|
||||
{
|
||||
policies.emplace_back(p->Clone());
|
||||
}
|
||||
policies.emplace_back(
|
||||
std::make_unique<Storage::Details::StorageRetryPolicy>(options.RetryOptions));
|
||||
for (const auto& p : options.PerRetryPolicies)
|
||||
{
|
||||
policies.emplace_back(p->Clone());
|
||||
}
|
||||
policies.emplace_back(std::make_unique<Storage::Details::StoragePerRetryPolicy>());
|
||||
policies.emplace_back(std::make_unique<Core::Http::BearerTokenAuthenticationPolicy>(
|
||||
credential, Storage::Details::StorageScope));
|
||||
policies.emplace_back(
|
||||
std::make_unique<Azure::Core::Http::TransportPolicy>(options.TransportPolicyOptions));
|
||||
m_pipeline = std::make_shared<Azure::Core::Http::HttpPipeline>(policies);
|
||||
|
||||
policies.clear();
|
||||
for (const auto& p : options.PerOperationPolicies)
|
||||
{
|
||||
policies.emplace_back(p->Clone());
|
||||
}
|
||||
for (const auto& p : options.PerRetryPolicies)
|
||||
{
|
||||
policies.emplace_back(p->Clone());
|
||||
}
|
||||
policies.emplace_back(std::make_unique<Storage::Details::StoragePerRetryPolicy>());
|
||||
policies.emplace_back(std::make_unique<Core::Http::BearerTokenAuthenticationPolicy>(
|
||||
credential, Storage::Details::StorageScope));
|
||||
policies.emplace_back(std::make_unique<NoopTransportPolicy>());
|
||||
m_subRequestPipeline = std::make_shared<Azure::Core::Http::HttpPipeline>(policies);
|
||||
}
|
||||
|
||||
BlobBatchClient::BlobBatchClient(const std::string& serviceUrl, const BlobClientOptions& options)
|
||||
: m_serviceUrl(serviceUrl)
|
||||
{
|
||||
std::vector<std::unique_ptr<Azure::Core::Http::HttpPolicy>> policies;
|
||||
policies.emplace_back(std::make_unique<Azure::Core::Http::TelemetryPolicy>(
|
||||
Storage::Details::BlobServicePackageName, Details::Version::VersionString()));
|
||||
policies.emplace_back(std::make_unique<Azure::Core::Http::RequestIdPolicy>());
|
||||
for (const auto& p : options.PerOperationPolicies)
|
||||
{
|
||||
policies.emplace_back(p->Clone());
|
||||
}
|
||||
policies.emplace_back(
|
||||
std::make_unique<Storage::Details::StorageRetryPolicy>(options.RetryOptions));
|
||||
for (const auto& p : options.PerRetryPolicies)
|
||||
{
|
||||
policies.emplace_back(p->Clone());
|
||||
}
|
||||
policies.emplace_back(std::make_unique<Storage::Details::StoragePerRetryPolicy>());
|
||||
policies.emplace_back(
|
||||
std::make_unique<Azure::Core::Http::TransportPolicy>(options.TransportPolicyOptions));
|
||||
m_pipeline = std::make_shared<Azure::Core::Http::HttpPipeline>(policies);
|
||||
|
||||
policies.clear();
|
||||
for (const auto& p : options.PerOperationPolicies)
|
||||
{
|
||||
policies.emplace_back(p->Clone());
|
||||
}
|
||||
for (const auto& p : options.PerRetryPolicies)
|
||||
{
|
||||
policies.emplace_back(p->Clone());
|
||||
}
|
||||
policies.emplace_back(std::make_unique<Storage::Details::StoragePerRetryPolicy>());
|
||||
policies.emplace_back(std::make_unique<NoopTransportPolicy>());
|
||||
m_subRequestPipeline = std::make_shared<Azure::Core::Http::HttpPipeline>(policies);
|
||||
}
|
||||
|
||||
Azure::Core::Response<SubmitBlobBatchResult> BlobBatchClient::SubmitBatch(
|
||||
const BlobBatch& batch,
|
||||
const SubmitBlobBatchOptions& options) const
|
||||
{
|
||||
const std::string LineEnding = "\r\n";
|
||||
const std::string ContentTypePrefix = "multipart/mixed; boundary=";
|
||||
|
||||
std::string boundary = "batch_" + Azure::Core::Uuid::CreateUuid().GetUuidString();
|
||||
|
||||
enum class RequestType
|
||||
{
|
||||
DeleteBlob,
|
||||
SetBlobAccessTier,
|
||||
};
|
||||
|
||||
std::vector<RequestType> requestTypes;
|
||||
|
||||
std::string requestBody;
|
||||
{
|
||||
auto getBatchBoundary = [&LineEnding, &boundary, subRequestCounter = 0]() mutable {
|
||||
std::string ret;
|
||||
ret += "--" + boundary + LineEnding;
|
||||
ret += "Content-Type: application/http" + LineEnding + "Content-Transfer-Encoding: binary"
|
||||
+ LineEnding + "Content-ID: " + std::to_string(subRequestCounter++) + LineEnding
|
||||
+ LineEnding;
|
||||
return ret;
|
||||
};
|
||||
for (const auto& subrequest : batch.m_deleteBlobSubRequests)
|
||||
{
|
||||
requestTypes.emplace_back(RequestType::DeleteBlob);
|
||||
|
||||
requestBody += getBatchBoundary();
|
||||
|
||||
auto blobUrl = m_serviceUrl;
|
||||
blobUrl.AppendPath(Storage::Details::UrlEncodePath(subrequest.BlobContainerName));
|
||||
blobUrl.AppendPath(Storage::Details::UrlEncodePath(subrequest.BlobName));
|
||||
Details::BlobRestClient::Blob::DeleteBlobOptions protocolLayerOptions;
|
||||
protocolLayerOptions.DeleteSnapshots = subrequest.Options.DeleteSnapshots;
|
||||
protocolLayerOptions.IfModifiedSince = subrequest.Options.AccessConditions.IfModifiedSince;
|
||||
protocolLayerOptions.IfUnmodifiedSince
|
||||
= subrequest.Options.AccessConditions.IfUnmodifiedSince;
|
||||
protocolLayerOptions.IfMatch = subrequest.Options.AccessConditions.IfMatch;
|
||||
protocolLayerOptions.IfNoneMatch = subrequest.Options.AccessConditions.IfNoneMatch;
|
||||
protocolLayerOptions.LeaseId = subrequest.Options.AccessConditions.LeaseId;
|
||||
auto message
|
||||
= Details::BlobRestClient::Blob::DeleteCreateMessage(blobUrl, protocolLayerOptions);
|
||||
message.RemoveHeader(Storage::Details::HttpHeaderXMsVersion);
|
||||
m_subRequestPipeline->Send(options.Context, message);
|
||||
requestBody += message.GetHTTPMessagePreBody();
|
||||
}
|
||||
for (const auto& subrequest : batch.m_setBlobAccessTierSubRequests)
|
||||
{
|
||||
requestTypes.emplace_back(RequestType::SetBlobAccessTier);
|
||||
|
||||
requestBody += getBatchBoundary();
|
||||
|
||||
auto blobUrl = m_serviceUrl;
|
||||
blobUrl.AppendPath(Storage::Details::UrlEncodePath(subrequest.BlobContainerName));
|
||||
blobUrl.AppendPath(Storage::Details::UrlEncodePath(subrequest.BlobName));
|
||||
Details::BlobRestClient::Blob::SetBlobAccessTierOptions protocolLayerOptions;
|
||||
protocolLayerOptions.Tier = subrequest.Tier;
|
||||
protocolLayerOptions.RehydratePriority = subrequest.Options.RehydratePriority;
|
||||
auto message = Details::BlobRestClient::Blob::SetAccessTierCreateMessage(
|
||||
blobUrl, protocolLayerOptions);
|
||||
message.RemoveHeader(Storage::Details::HttpHeaderXMsVersion);
|
||||
m_subRequestPipeline->Send(options.Context, message);
|
||||
requestBody += message.GetHTTPMessagePreBody();
|
||||
}
|
||||
requestBody += "--" + boundary + "--" + LineEnding;
|
||||
}
|
||||
|
||||
Details::BlobRestClient::BlobBatch::SubmitBlobBatchOptions protocolLayerOptions;
|
||||
protocolLayerOptions.ContentType = ContentTypePrefix + boundary;
|
||||
|
||||
Azure::Core::Http::MemoryBodyStream requestBodyStream(
|
||||
reinterpret_cast<const uint8_t*>(requestBody.data()), requestBody.length());
|
||||
|
||||
auto rawResponse = Details::BlobRestClient::BlobBatch::SubmitBatch(
|
||||
options.Context, *m_pipeline, m_serviceUrl, &requestBodyStream, protocolLayerOptions);
|
||||
|
||||
if (rawResponse->ContentType.substr(0, ContentTypePrefix.length()) == ContentTypePrefix)
|
||||
{
|
||||
boundary = rawResponse->ContentType.substr(ContentTypePrefix.length());
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error("failed to parse Content-Type response header");
|
||||
}
|
||||
|
||||
SubmitBlobBatchResult batchResult;
|
||||
{
|
||||
const std::vector<uint8_t>& responseBody = rawResponse.GetRawResponse().GetBody();
|
||||
|
||||
const char* const startPos = reinterpret_cast<const char*>(responseBody.data());
|
||||
const char* currPos = startPos;
|
||||
const char* const endPos = currPos + responseBody.size();
|
||||
|
||||
auto parseLookAhead = [&currPos, endPos](const std::string& expect) -> bool {
|
||||
// This doesn't move currPos
|
||||
for (std::size_t i = 0; i < expect.length(); ++i)
|
||||
{
|
||||
if (currPos + i < endPos && currPos[i] == expect[i])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
auto parseConsume = [&currPos, startPos, &parseLookAhead](const std::string& expect) -> void {
|
||||
// This moves currPos
|
||||
if (parseLookAhead(expect))
|
||||
{
|
||||
currPos += expect.length();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"failed to parse response body at " + std::to_string(currPos - startPos));
|
||||
}
|
||||
};
|
||||
|
||||
auto parseFindNext = [&currPos, endPos](const std::string& expect) -> const char* {
|
||||
// This doesn't move currPos
|
||||
return std::search(currPos, endPos, expect.begin(), expect.end());
|
||||
};
|
||||
|
||||
auto parseFindNextAfter = [endPos, &parseFindNext](const std::string& expect) -> const char* {
|
||||
// This doesn't move currPos
|
||||
return std::min(endPos, parseFindNext(expect) + expect.length());
|
||||
};
|
||||
|
||||
auto parseGetUntilAfter
|
||||
= [&currPos, endPos, &parseFindNext](const std::string& expect) -> std::string {
|
||||
// This moves currPos
|
||||
auto ePos = parseFindNext(expect);
|
||||
std::string ret(currPos, ePos);
|
||||
currPos = std::min(endPos, ePos + expect.length());
|
||||
return ret;
|
||||
};
|
||||
|
||||
int subRequestCounter = 0;
|
||||
while (true)
|
||||
{
|
||||
parseConsume("--" + boundary);
|
||||
|
||||
if (parseLookAhead("--"))
|
||||
{
|
||||
parseConsume("--");
|
||||
}
|
||||
|
||||
if (currPos == endPos)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
currPos = parseFindNextAfter(LineEnding + LineEnding);
|
||||
auto boundaryPos = parseFindNext("--" + boundary);
|
||||
|
||||
// now (currPos, boundaryPos) is a subresponse body
|
||||
parseConsume("HTTP/");
|
||||
int32_t httpMajorVersion = std::stoi(parseGetUntilAfter("."));
|
||||
int32_t httpMinorVersion = std::stoi(parseGetUntilAfter(" "));
|
||||
int32_t httpStatusCode = std::stoi(parseGetUntilAfter(" "));
|
||||
std::string httpReasonPhrase = parseGetUntilAfter(LineEnding);
|
||||
|
||||
auto rawSubresponse = std::make_unique<Azure::Core::Http::RawResponse>(
|
||||
httpMajorVersion,
|
||||
httpMinorVersion,
|
||||
static_cast<Azure::Core::Http::HttpStatusCode>(httpStatusCode),
|
||||
httpReasonPhrase);
|
||||
|
||||
while (currPos < boundaryPos)
|
||||
{
|
||||
if (parseLookAhead(LineEnding))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
std::string headerName = parseGetUntilAfter(": ");
|
||||
std::string headerValue = parseGetUntilAfter(LineEnding);
|
||||
rawSubresponse->AddHeader(headerName, headerValue);
|
||||
}
|
||||
|
||||
parseConsume(LineEnding);
|
||||
|
||||
rawSubresponse->SetBody(std::vector<uint8_t>(currPos, boundaryPos));
|
||||
currPos = boundaryPos;
|
||||
|
||||
RequestType requestType = requestTypes[subRequestCounter++];
|
||||
if (requestType == RequestType::DeleteBlob)
|
||||
{
|
||||
try
|
||||
{
|
||||
batchResult.DeleteBlobResults.emplace_back(
|
||||
Details::BlobRestClient::Blob::DeleteCreateResponse(
|
||||
options.Context, std::move(rawSubresponse)));
|
||||
}
|
||||
catch (StorageException& e)
|
||||
{
|
||||
batchResult.DeleteBlobResults.emplace_back(
|
||||
Azure::Core::Response<Models::DeleteBlobResult>(
|
||||
Models::DeleteBlobResult{}, std::move(e.RawResponse)));
|
||||
}
|
||||
}
|
||||
else if (requestType == RequestType::SetBlobAccessTier)
|
||||
{
|
||||
try
|
||||
{
|
||||
batchResult.SetBlobAccessTierResults.emplace_back(
|
||||
Details::BlobRestClient::Blob::SetAccessTierCreateResponse(
|
||||
options.Context, std::move(rawSubresponse)));
|
||||
}
|
||||
catch (StorageException& e)
|
||||
{
|
||||
batchResult.SetBlobAccessTierResults.emplace_back(
|
||||
Azure::Core::Response<Models::SetBlobAccessTierResult>(
|
||||
Models::SetBlobAccessTierResult{}, std::move(e.RawResponse)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Azure::Core::Response<SubmitBlobBatchResult>(
|
||||
std::move(batchResult), rawResponse.ExtractRawResponse());
|
||||
}
|
||||
}}} // namespace Azure::Storage::Blobs
|
||||
@ -1,152 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include <thread>
|
||||
|
||||
#include <azure/storage/blobs.hpp>
|
||||
#include <azure/storage/blobs/blob_sas_builder.hpp>
|
||||
|
||||
#include "test_base.hpp"
|
||||
|
||||
namespace Azure { namespace Storage { namespace Test {
|
||||
|
||||
class BlobBatchClientTest : public ::testing::Test {
|
||||
protected:
|
||||
BlobBatchClientTest()
|
||||
: m_blobBatchClient(Azure::Storage::Blobs::BlobBatchClient::CreateFromConnectionString(
|
||||
StandardStorageConnectionString()))
|
||||
{
|
||||
}
|
||||
|
||||
Azure::Storage::Blobs::BlobBatchClient m_blobBatchClient;
|
||||
};
|
||||
|
||||
TEST_F(BlobBatchClientTest, BatchSasAuth)
|
||||
{
|
||||
Sas::AccountSasBuilder accountSasBuilder;
|
||||
accountSasBuilder.Protocol = Sas::SasProtocol::HttpsAndHttp;
|
||||
accountSasBuilder.StartsOn = Azure::Core::DateTime::Now() - std::chrono::minutes(5);
|
||||
accountSasBuilder.ExpiresOn = Azure::Core::DateTime::Now() + std::chrono::minutes(60);
|
||||
accountSasBuilder.Services = Sas::AccountSasServices::Blobs;
|
||||
accountSasBuilder.ResourceTypes
|
||||
= Sas::AccountSasResource::Object | Sas::AccountSasResource::BlobContainer;
|
||||
accountSasBuilder.SetPermissions(Sas::AccountSasPermissions::All);
|
||||
auto keyCredential
|
||||
= Details::ParseConnectionString(StandardStorageConnectionString()).KeyCredential;
|
||||
|
||||
auto serviceClient
|
||||
= Blobs::BlobServiceClient::CreateFromConnectionString(StandardStorageConnectionString());
|
||||
std::string containerName = LowercaseRandomString();
|
||||
auto containerClient = serviceClient.GetBlobContainerClient(containerName);
|
||||
containerClient.Create();
|
||||
std::string blobName = RandomString();
|
||||
auto blobClient = containerClient.GetBlockBlobClient(blobName);
|
||||
blobClient.UploadFrom(nullptr, 0);
|
||||
|
||||
auto batch = Azure::Storage::Blobs::BlobBatchClient::CreateBatch();
|
||||
batch.DeleteBlob(containerName, blobName);
|
||||
|
||||
auto batchClient = Blobs::BlobBatchClient(serviceClient.GetUrl());
|
||||
|
||||
EXPECT_THROW(batchClient.SubmitBatch(batch), StorageException);
|
||||
|
||||
batchClient = Blobs::BlobBatchClient(
|
||||
serviceClient.GetUrl() + accountSasBuilder.GenerateSasToken(*keyCredential));
|
||||
|
||||
EXPECT_NO_THROW(batchClient.SubmitBatch(batch));
|
||||
|
||||
containerClient.Delete();
|
||||
}
|
||||
|
||||
TEST_F(BlobBatchClientTest, Batch)
|
||||
{
|
||||
auto serviceClient
|
||||
= Blobs::BlobServiceClient::CreateFromConnectionString(StandardStorageConnectionString());
|
||||
std::string containerName1 = LowercaseRandomString();
|
||||
std::string containerName2 = LowercaseRandomString();
|
||||
auto containerClient1 = serviceClient.GetBlobContainerClient(containerName1);
|
||||
containerClient1.Create();
|
||||
auto containerClient2 = serviceClient.GetBlobContainerClient(containerName2);
|
||||
containerClient2.Create();
|
||||
|
||||
std::string blobName11 = RandomString();
|
||||
auto blobClient11 = containerClient1.GetBlockBlobClient(blobName11);
|
||||
blobClient11.UploadFrom(nullptr, 0);
|
||||
|
||||
std::string blobName12 = RandomString();
|
||||
auto blobClient12 = containerClient1.GetBlockBlobClient(blobName12);
|
||||
blobClient12.UploadFrom(nullptr, 0);
|
||||
|
||||
std::string blobName21 = RandomString();
|
||||
auto blobClient21 = containerClient2.GetBlockBlobClient(blobName21);
|
||||
blobClient21.UploadFrom(nullptr, 0);
|
||||
|
||||
std::string blobName22 = RandomString();
|
||||
|
||||
auto batch = Azure::Storage::Blobs::BlobBatchClient::CreateBatch();
|
||||
int32_t id1
|
||||
= batch.SetBlobAccessTier(containerName1, blobName11, Blobs::Models::AccessTier::Cool);
|
||||
int32_t id2
|
||||
= batch.SetBlobAccessTier(containerName1, blobName12, Blobs::Models::AccessTier::Hot);
|
||||
int32_t id3
|
||||
= batch.SetBlobAccessTier(containerName2, blobName21, Blobs::Models::AccessTier::Hot);
|
||||
int32_t id4
|
||||
= batch.SetBlobAccessTier(containerName2, blobName22, Blobs::Models::AccessTier::Cool);
|
||||
unused(id1, id2, id3, id4);
|
||||
|
||||
std::size_t failedId = static_cast<std::size_t>(id4);
|
||||
std::size_t batchSize = static_cast<std::size_t>(id4) + 1;
|
||||
|
||||
auto batchResult = m_blobBatchClient.SubmitBatch(batch);
|
||||
EXPECT_EQ(batchResult->SetBlobAccessTierResults.size(), batchSize);
|
||||
EXPECT_TRUE(batchResult->DeleteBlobResults.empty());
|
||||
for (std::size_t i = 0; i < batchSize; ++i)
|
||||
{
|
||||
if (i != failedId)
|
||||
{
|
||||
EXPECT_EQ(
|
||||
batchResult->SetBlobAccessTierResults[i].GetRawResponse().GetStatusCode(),
|
||||
Azure::Core::Http::HttpStatusCode::Ok);
|
||||
}
|
||||
else
|
||||
{
|
||||
EXPECT_NE(
|
||||
batchResult->SetBlobAccessTierResults[i].GetRawResponse().GetStatusCode(),
|
||||
Azure::Core::Http::HttpStatusCode::Ok);
|
||||
}
|
||||
}
|
||||
|
||||
batch = Azure::Storage::Blobs::BlobBatchClient::CreateBatch();
|
||||
id1 = batch.DeleteBlob(containerName1, blobName11);
|
||||
id2 = batch.DeleteBlob(containerName1, blobName12);
|
||||
id3 = batch.DeleteBlob(containerName2, blobName21);
|
||||
id4 = batch.DeleteBlob(containerName2, blobName22);
|
||||
|
||||
failedId = static_cast<std::size_t>(id4);
|
||||
batchSize = static_cast<std::size_t>(id4) + 1;
|
||||
|
||||
batchResult = m_blobBatchClient.SubmitBatch(batch);
|
||||
|
||||
EXPECT_EQ(batchResult->DeleteBlobResults.size(), batchSize);
|
||||
EXPECT_TRUE(batchResult->SetBlobAccessTierResults.empty());
|
||||
for (std::size_t i = 0; i < batchSize; ++i)
|
||||
{
|
||||
if (i != failedId)
|
||||
{
|
||||
EXPECT_EQ(
|
||||
batchResult->DeleteBlobResults[i].GetRawResponse().GetStatusCode(),
|
||||
Azure::Core::Http::HttpStatusCode::Accepted);
|
||||
}
|
||||
else
|
||||
{
|
||||
EXPECT_NE(
|
||||
batchResult->DeleteBlobResults[i].GetRawResponse().GetStatusCode(),
|
||||
Azure::Core::Http::HttpStatusCode::Accepted);
|
||||
}
|
||||
}
|
||||
|
||||
containerClient1.Delete();
|
||||
containerClient2.Delete();
|
||||
}
|
||||
|
||||
}}} // namespace Azure::Storage::Test
|
||||
Loading…
Reference in New Issue
Block a user