[Storage Blob Service] Container Lease and Blob Lease (#402)

* container lease

* blob lease
This commit is contained in:
JinmingHu 2020-08-06 13:50:15 +08:00 committed by GitHub
parent 46c0af9a70
commit 355184ef4f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 1146 additions and 2 deletions

View File

@ -38,6 +38,7 @@ set(AZURE_STORAGE_COMMON_SOURCE
src/common/file_io.cpp
src/common/reliable_stream.cpp
src/common/shared_key_policy.cpp
src/common/storage_common.cpp
src/common/storage_credential.cpp
src/common/storage_error.cpp
src/common/storage_uri_builder.cpp

View File

@ -284,6 +284,76 @@ namespace Azure { namespace Storage { namespace Blobs {
Azure::Core::Response<UndeleteBlobInfo> Undelete(
const UndeleteBlobOptions& options = UndeleteBlobOptions()) const;
/**
* @brief Acquires a lease on the blob.
*
* @param proposedLeaseId
* Proposed lease ID, in a GUID string format.
* @param duration Specifies the duration of
* the lease, in seconds, or Azure::Storage::c_InfiniteLeaseDuration for a lease that never
* expires. A non-infinite lease can be between 15 and 60 seconds. A lease duration cannot be
* changed using renew or change.
* @param options Optional parameters to execute this
* function.
* @return A BlobLease describing the lease.
*/
Azure::Core::Response<BlobLease> AcquireLease(
const std::string& proposedLeaseId,
int32_t duration,
const AcquireBlobLeaseOptions& options = AcquireBlobLeaseOptions()) const;
/**
* @brief Renews the blob's previously-acquired lease.
*
* @param
* leaseId ID of the previously-acquired lease.
* @param options Optional parameters to
* execute this function.
* @return A BlobLease describing the lease.
*/
Azure::Core::Response<BlobLease> RenewLease(
const std::string& leaseId,
const RenewBlobLeaseOptions& options = RenewBlobLeaseOptions()) const;
/**
* @brief Releases the blob's previously-acquired lease.
*
* @param
* leaseId ID of the previously-acquired lease.
* @param options Optional parameters to
* execute this function.
* @return A BlobInfo describing the updated container.
*/
Azure::Core::Response<BlobInfo> ReleaseLease(
const std::string& leaseId,
const ReleaseBlobLeaseOptions& options = ReleaseBlobLeaseOptions()) const;
/**
* @brief Changes the lease of an active lease.
*
* @param leaseId ID of the
* previously-acquired lease.
* @param proposedLeaseId Proposed lease ID, in a GUID string
* format.
* @param options Optional parameters to execute this function.
* @return A
* BlobLease describing the lease.
*/
Azure::Core::Response<BlobLease> ChangeLease(
const std::string& leaseId,
const std::string& proposedLeaseId,
const ChangeBlobLeaseOptions& options = ChangeBlobLeaseOptions()) const;
/**
* @brief Breaks the previously-acquired lease.
*
* @param options Optional
* parameters to execute this function.
* @return A BrokenLease describing the broken lease.
*/
Azure::Core::Response<BrokenLease> BreakLease(
const BreakBlobLeaseOptions& options = BreakBlobLeaseOptions()) const;
protected:
UriBuilder m_blobUrl;
std::shared_ptr<Azure::Core::Http::HttpPipeline> m_pipeline;

View File

@ -206,7 +206,7 @@ namespace Azure { namespace Storage { namespace Blobs {
/**
* @brief Gets the permissions for this container. The permissions indicate whether
* container data may be accessed publicly.
*
*
* @param options Optional parameters to
* execute this function.
* @return A BlobContainerAccessPolicy describing the container's
@ -219,7 +219,7 @@ namespace Azure { namespace Storage { namespace Blobs {
/**
* @brief Sets the permissions for the specified container. The permissions indicate
* whether blob container data may be accessed publicly.
*
*
* @param options Optional
* parameters to execute this function.
* @return A BlobContainerInfo describing the
@ -229,6 +229,76 @@ namespace Azure { namespace Storage { namespace Blobs {
const SetBlobContainerAccessPolicyOptions& options
= SetBlobContainerAccessPolicyOptions()) const;
/**
* @brief Acquires a lease on the container.
*
* @param proposedLeaseId
* Proposed lease ID, in a GUID string format.
* @param duration Specifies the duration of
* the lease, in seconds, or Azure::Storage::c_InfiniteLeaseDuration for a lease that never
* expires. A non-infinite lease can be between 15 and 60 seconds. A lease duration cannot be
* changed using renew or change.
* @param options Optional parameters to execute this
* function.
* @return A BlobLease describing the lease.
*/
Azure::Core::Response<BlobLease> AcquireLease(
const std::string& proposedLeaseId,
int32_t duration,
const AcquireBlobContainerLeaseOptions& options = AcquireBlobContainerLeaseOptions()) const;
/**
* @brief Renews the container's previously-acquired lease.
*
* @param
* leaseId ID of the previously-acquired lease.
* @param options Optional parameters to
* execute this function.
* @return A BlobLease describing the lease.
*/
Azure::Core::Response<BlobLease> RenewLease(
const std::string& leaseId,
const RenewBlobContainerLeaseOptions& options = RenewBlobContainerLeaseOptions()) const;
/**
* @brief Releases the container's previously-acquired lease.
*
* @param
* leaseId ID of the previously-acquired lease.
* @param options Optional parameters to
* execute this function.
* @return A BlobContainerInfo describing the updated container.
*/
Azure::Core::Response<BlobContainerInfo> ReleaseLease(
const std::string& leaseId,
const ReleaseBlobContainerLeaseOptions& options = ReleaseBlobContainerLeaseOptions()) const;
/**
* @brief Changes the lease of an active lease.
*
* @param leaseId ID of the
* previously-acquired lease.
* @param proposedLeaseId Proposed lease ID, in a GUID string
* format.
* @param options Optional parameters to execute this function.
* @return A
* BlobLease describing the lease.
*/
Azure::Core::Response<BlobLease> ChangeLease(
const std::string& leaseId,
const std::string& proposedLeaseId,
const ChangeBlobContainerLeaseOptions& options = ChangeBlobContainerLeaseOptions()) const;
/**
* @brief Breaks the previously-acquired lease.
*
* @param options Optional
* parameters to execute this function.
* @return A BrokenLease describing the broken lease.
*/
Azure::Core::Response<BrokenLease> BreakLease(
const BreakBlobContainerLeaseOptions& options = BreakBlobContainerLeaseOptions()) const;
private:
UriBuilder m_containerUrl;
std::shared_ptr<Azure::Core::Http::HttpPipeline> m_pipeline;

View File

@ -350,6 +350,70 @@ namespace Azure { namespace Storage { namespace Blobs {
ContainerAccessConditions AccessConditions;
};
/**
* @brief Optional parameters for BlobContainerClient::AcquireLease.
*/
struct AcquireBlobContainerLeaseOptions : public LastModifiedTimeAccessConditions
{
/**
* @brief Context for cancelling long running operations.
*/
Azure::Core::Context Context;
};
/**
* @brief Optional parameters for BlobContainerClient::RenewLease.
*/
struct RenewBlobContainerLeaseOptions : public LastModifiedTimeAccessConditions
{
/**
* @brief Context for cancelling long running operations.
*/
Azure::Core::Context Context;
};
/**
* @brief Optional parameters for BlobContainerClient::ChangeLease.
*/
struct ChangeBlobContainerLeaseOptions : public LastModifiedTimeAccessConditions
{
/**
* @brief Context for cancelling long running operations.
*/
Azure::Core::Context Context;
};
/**
* @brief Optional parameters for BlobContainerClient::ReleaseLease.
*/
struct ReleaseBlobContainerLeaseOptions : public LastModifiedTimeAccessConditions
{
/**
* @brief Context for cancelling long running operations.
*/
Azure::Core::Context Context;
};
/**
* @brief Optional parameters for BlobContainerClient::BreakLease.
*/
struct BreakBlobContainerLeaseOptions : public LastModifiedTimeAccessConditions
{
/**
* @brief Context for cancelling long running operations.
*/
Azure::Core::Context Context;
/**
* @brief Proposed duration the lease should continue before it is broken, in seconds,
* between 0 and 60. This break period is only used if it is shorter than the time remaining on
* the lease. If longer, the time remaining on the lease is used. A new lease will not be
* available before the break period has expired, but the lease may be held for longer than the
* break period.
*/
Azure::Core::Nullable<int32_t> breakPeriod;
};
/**
* @brief Blob client options used to initalize BlobClient.
*/
@ -642,6 +706,75 @@ namespace Azure { namespace Storage { namespace Blobs {
Azure::Core::Context Context;
};
/**
* @brief Optional parameters for BlobClient::AcquireLease.
*/
struct AcquireBlobLeaseOptions : public LastModifiedTimeAccessConditions,
public ETagAccessConditions
{
/**
* @brief Context for cancelling long running operations.
*/
Azure::Core::Context Context;
};
/**
* @brief Optional parameters for BlobClient::RenewLease.
*/
struct RenewBlobLeaseOptions : public LastModifiedTimeAccessConditions,
public ETagAccessConditions
{
/**
* @brief Context for cancelling long running operations.
*/
Azure::Core::Context Context;
};
/**
* @brief Optional parameters for BlobClient::ChangeLease.
*/
struct ChangeBlobLeaseOptions : public LastModifiedTimeAccessConditions,
public ETagAccessConditions
{
/**
* @brief Context for cancelling long running operations.
*/
Azure::Core::Context Context;
};
/**
* @brief Optional parameters for BlobClient::ReleaseLease.
*/
struct ReleaseBlobLeaseOptions : public LastModifiedTimeAccessConditions,
public ETagAccessConditions
{
/**
* @brief Context for cancelling long running operations.
*/
Azure::Core::Context Context;
};
/**
* @brief Optional parameters for BlobClient::BreakLease.
*/
struct BreakBlobLeaseOptions : public LastModifiedTimeAccessConditions,
public ETagAccessConditions
{
/**
* @brief Context for cancelling long running operations.
*/
Azure::Core::Context Context;
/**
* @brief Proposed duration the lease should continue before it is broken, in seconds,
* between 0 and 60. This break period is only used if it is shorter than the time remaining on
* the lease. If longer, the time remaining on the lease is used. A new lease will not be
* available before the break period has expired, but the lease may be held for longer than the
* break period.
*/
Azure::Core::Nullable<int32_t> breakPeriod;
};
/**
* @brief Optional parameters for BlockBlobClient::Upload.
*/

View File

@ -382,6 +382,13 @@ namespace Azure { namespace Storage { namespace Blobs {
Azure::Core::Nullable<int64_t> SequenceNumber;
}; // struct BlobInfo
struct BlobLease
{
std::string ETag;
std::string LastModified;
std::string LeaseId;
}; // struct BlobLease
enum class BlobLeaseState
{
Available,
@ -637,6 +644,13 @@ namespace Azure { namespace Storage { namespace Blobs {
throw std::runtime_error("cannot convert " + block_type + " to BlockType");
}
struct BrokenLease
{
std::string ETag;
std::string LastModified;
int32_t LeaseTime = 0;
}; // struct BrokenLease
enum class CopyStatus
{
Unknown,
@ -3223,6 +3237,267 @@ namespace Azure { namespace Storage { namespace Blobs {
std::move(response), std::move(pHttpResponse));
}
struct AcquireLeaseOptions
{
Azure::Core::Nullable<int32_t> Timeout;
int32_t LeaseDuration = -1;
Azure::Core::Nullable<std::string> ProposedLeaseId;
Azure::Core::Nullable<std::string> IfModifiedSince;
Azure::Core::Nullable<std::string> IfUnmodifiedSince;
}; // struct AcquireLeaseOptions
static Azure::Core::Response<BlobLease> AcquireLease(
Azure::Core::Context context,
Azure::Core::Http::HttpPipeline& pipeline,
const std::string& url,
const AcquireLeaseOptions& options)
{
unused(options);
auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url);
request.AddHeader("Content-Length", "0");
request.AddHeader("x-ms-version", c_ApiVersion);
if (options.Timeout.HasValue())
{
request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue()));
}
request.AddQueryParameter("restype", "container");
request.AddQueryParameter("comp", "lease");
request.AddHeader("x-ms-lease-action", "acquire");
request.AddHeader("x-ms-lease-duration", std::to_string(options.LeaseDuration));
if (options.ProposedLeaseId.HasValue())
{
request.AddHeader("x-ms-proposed-lease-id", options.ProposedLeaseId.GetValue());
}
if (options.IfModifiedSince.HasValue())
{
request.AddHeader("If-Modified-Since", options.IfModifiedSince.GetValue());
}
if (options.IfUnmodifiedSince.HasValue())
{
request.AddHeader("If-Unmodified-Since", options.IfUnmodifiedSince.GetValue());
}
auto pHttpResponse = pipeline.Send(context, request);
Azure::Core::Http::RawResponse& httpResponse = *pHttpResponse;
BlobLease 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));
}
response.ETag = httpResponse.GetHeaders().at("etag");
response.LastModified = httpResponse.GetHeaders().at("last-modified");
response.LeaseId = httpResponse.GetHeaders().at("x-ms-lease-id");
return Azure::Core::Response<BlobLease>(std::move(response), std::move(pHttpResponse));
}
struct RenewLeaseOptions
{
Azure::Core::Nullable<int32_t> Timeout;
std::string LeaseId;
Azure::Core::Nullable<std::string> IfModifiedSince;
Azure::Core::Nullable<std::string> IfUnmodifiedSince;
}; // struct RenewLeaseOptions
static Azure::Core::Response<BlobLease> RenewLease(
Azure::Core::Context context,
Azure::Core::Http::HttpPipeline& pipeline,
const std::string& url,
const RenewLeaseOptions& options)
{
unused(options);
auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url);
request.AddHeader("Content-Length", "0");
request.AddHeader("x-ms-version", c_ApiVersion);
if (options.Timeout.HasValue())
{
request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue()));
}
request.AddQueryParameter("restype", "container");
request.AddQueryParameter("comp", "lease");
request.AddHeader("x-ms-lease-action", "renew");
request.AddHeader("x-ms-lease-id", options.LeaseId);
if (options.IfModifiedSince.HasValue())
{
request.AddHeader("If-Modified-Since", options.IfModifiedSince.GetValue());
}
if (options.IfUnmodifiedSince.HasValue())
{
request.AddHeader("If-Unmodified-Since", options.IfUnmodifiedSince.GetValue());
}
auto pHttpResponse = pipeline.Send(context, request);
Azure::Core::Http::RawResponse& httpResponse = *pHttpResponse;
BlobLease response;
auto http_status_code
= static_cast<std::underlying_type<Azure::Core::Http::HttpStatusCode>::type>(
httpResponse.GetStatusCode());
if (!(http_status_code == 200))
{
throw StorageError::CreateFromResponse(context, std::move(pHttpResponse));
}
response.ETag = httpResponse.GetHeaders().at("etag");
response.LastModified = httpResponse.GetHeaders().at("last-modified");
response.LeaseId = httpResponse.GetHeaders().at("x-ms-lease-id");
return Azure::Core::Response<BlobLease>(std::move(response), std::move(pHttpResponse));
}
struct ChangeLeaseOptions
{
Azure::Core::Nullable<int32_t> Timeout;
std::string LeaseId;
std::string ProposedLeaseId;
Azure::Core::Nullable<std::string> IfModifiedSince;
Azure::Core::Nullable<std::string> IfUnmodifiedSince;
}; // struct ChangeLeaseOptions
static Azure::Core::Response<BlobLease> ChangeLease(
Azure::Core::Context context,
Azure::Core::Http::HttpPipeline& pipeline,
const std::string& url,
const ChangeLeaseOptions& options)
{
unused(options);
auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url);
request.AddHeader("Content-Length", "0");
request.AddHeader("x-ms-version", c_ApiVersion);
if (options.Timeout.HasValue())
{
request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue()));
}
request.AddQueryParameter("restype", "container");
request.AddQueryParameter("comp", "lease");
request.AddHeader("x-ms-lease-action", "change");
request.AddHeader("x-ms-lease-id", options.LeaseId);
request.AddHeader("x-ms-proposed-lease-id", options.ProposedLeaseId);
if (options.IfModifiedSince.HasValue())
{
request.AddHeader("If-Modified-Since", options.IfModifiedSince.GetValue());
}
if (options.IfUnmodifiedSince.HasValue())
{
request.AddHeader("If-Unmodified-Since", options.IfUnmodifiedSince.GetValue());
}
auto pHttpResponse = pipeline.Send(context, request);
Azure::Core::Http::RawResponse& httpResponse = *pHttpResponse;
BlobLease response;
auto http_status_code
= static_cast<std::underlying_type<Azure::Core::Http::HttpStatusCode>::type>(
httpResponse.GetStatusCode());
if (!(http_status_code == 200))
{
throw StorageError::CreateFromResponse(context, std::move(pHttpResponse));
}
response.ETag = httpResponse.GetHeaders().at("etag");
response.LastModified = httpResponse.GetHeaders().at("last-modified");
response.LeaseId = httpResponse.GetHeaders().at("x-ms-lease-id");
return Azure::Core::Response<BlobLease>(std::move(response), std::move(pHttpResponse));
}
struct ReleaseLeaseOptions
{
Azure::Core::Nullable<int32_t> Timeout;
std::string LeaseId;
Azure::Core::Nullable<std::string> IfModifiedSince;
Azure::Core::Nullable<std::string> IfUnmodifiedSince;
}; // struct ReleaseLeaseOptions
static Azure::Core::Response<BlobContainerInfo> ReleaseLease(
Azure::Core::Context context,
Azure::Core::Http::HttpPipeline& pipeline,
const std::string& url,
const ReleaseLeaseOptions& options)
{
unused(options);
auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url);
request.AddHeader("Content-Length", "0");
request.AddHeader("x-ms-version", c_ApiVersion);
if (options.Timeout.HasValue())
{
request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue()));
}
request.AddQueryParameter("restype", "container");
request.AddQueryParameter("comp", "lease");
request.AddHeader("x-ms-lease-action", "release");
request.AddHeader("x-ms-lease-id", options.LeaseId);
if (options.IfModifiedSince.HasValue())
{
request.AddHeader("If-Modified-Since", options.IfModifiedSince.GetValue());
}
if (options.IfUnmodifiedSince.HasValue())
{
request.AddHeader("If-Unmodified-Since", options.IfUnmodifiedSince.GetValue());
}
auto pHttpResponse = pipeline.Send(context, request);
Azure::Core::Http::RawResponse& httpResponse = *pHttpResponse;
BlobContainerInfo response;
auto http_status_code
= static_cast<std::underlying_type<Azure::Core::Http::HttpStatusCode>::type>(
httpResponse.GetStatusCode());
if (!(http_status_code == 200))
{
throw StorageError::CreateFromResponse(context, std::move(pHttpResponse));
}
response.ETag = httpResponse.GetHeaders().at("etag");
response.LastModified = httpResponse.GetHeaders().at("last-modified");
return Azure::Core::Response<BlobContainerInfo>(
std::move(response), std::move(pHttpResponse));
}
struct BreakLeaseOptions
{
Azure::Core::Nullable<int32_t> Timeout;
Azure::Core::Nullable<int32_t> BreakPeriod;
Azure::Core::Nullable<std::string> IfModifiedSince;
Azure::Core::Nullable<std::string> IfUnmodifiedSince;
}; // struct BreakLeaseOptions
static Azure::Core::Response<BrokenLease> BreakLease(
Azure::Core::Context context,
Azure::Core::Http::HttpPipeline& pipeline,
const std::string& url,
const BreakLeaseOptions& options)
{
unused(options);
auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url);
request.AddHeader("Content-Length", "0");
request.AddHeader("x-ms-version", c_ApiVersion);
if (options.Timeout.HasValue())
{
request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue()));
}
request.AddQueryParameter("restype", "container");
request.AddQueryParameter("comp", "lease");
request.AddHeader("x-ms-lease-action", "break");
if (options.BreakPeriod.HasValue())
{
request.AddHeader(
"x-ms-lease-break-period", std::to_string(options.BreakPeriod.GetValue()));
}
if (options.IfModifiedSince.HasValue())
{
request.AddHeader("If-Modified-Since", options.IfModifiedSince.GetValue());
}
if (options.IfUnmodifiedSince.HasValue())
{
request.AddHeader("If-Unmodified-Since", options.IfUnmodifiedSince.GetValue());
}
auto pHttpResponse = pipeline.Send(context, request);
Azure::Core::Http::RawResponse& httpResponse = *pHttpResponse;
BrokenLease response;
auto http_status_code
= static_cast<std::underlying_type<Azure::Core::Http::HttpStatusCode>::type>(
httpResponse.GetStatusCode());
if (!(http_status_code == 202))
{
throw StorageError::CreateFromResponse(context, std::move(pHttpResponse));
}
response.ETag = httpResponse.GetHeaders().at("etag");
response.LastModified = httpResponse.GetHeaders().at("last-modified");
response.LeaseTime = std::stoi(httpResponse.GetHeaders().at("x-ms-lease-time"));
return Azure::Core::Response<BrokenLease>(std::move(response), std::move(pHttpResponse));
}
private:
static BlobContainerAccessPolicy BlobContainerAccessPolicyFromXml(XmlReader& reader)
{
@ -4993,6 +5268,317 @@ namespace Azure { namespace Storage { namespace Blobs {
std::move(response), std::move(pHttpResponse));
}
struct AcquireLeaseOptions
{
Azure::Core::Nullable<int32_t> Timeout;
int32_t LeaseDuration = -1;
Azure::Core::Nullable<std::string> ProposedLeaseId;
Azure::Core::Nullable<std::string> IfModifiedSince;
Azure::Core::Nullable<std::string> IfUnmodifiedSince;
Azure::Core::Nullable<std::string> IfMatch;
Azure::Core::Nullable<std::string> IfNoneMatch;
}; // struct AcquireLeaseOptions
static Azure::Core::Response<BlobLease> AcquireLease(
Azure::Core::Context context,
Azure::Core::Http::HttpPipeline& pipeline,
const std::string& url,
const AcquireLeaseOptions& options)
{
unused(options);
auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url);
request.AddHeader("Content-Length", "0");
request.AddHeader("x-ms-version", c_ApiVersion);
if (options.Timeout.HasValue())
{
request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue()));
}
request.AddQueryParameter("comp", "lease");
request.AddHeader("x-ms-lease-action", "acquire");
request.AddHeader("x-ms-lease-duration", std::to_string(options.LeaseDuration));
if (options.ProposedLeaseId.HasValue())
{
request.AddHeader("x-ms-proposed-lease-id", options.ProposedLeaseId.GetValue());
}
if (options.IfModifiedSince.HasValue())
{
request.AddHeader("If-Modified-Since", options.IfModifiedSince.GetValue());
}
if (options.IfUnmodifiedSince.HasValue())
{
request.AddHeader("If-Unmodified-Since", options.IfUnmodifiedSince.GetValue());
}
if (options.IfMatch.HasValue())
{
request.AddHeader("If-Match", options.IfMatch.GetValue());
}
if (options.IfNoneMatch.HasValue())
{
request.AddHeader("If-None-Match", options.IfNoneMatch.GetValue());
}
auto pHttpResponse = pipeline.Send(context, request);
Azure::Core::Http::RawResponse& httpResponse = *pHttpResponse;
BlobLease 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));
}
response.ETag = httpResponse.GetHeaders().at("etag");
response.LastModified = httpResponse.GetHeaders().at("last-modified");
response.LeaseId = httpResponse.GetHeaders().at("x-ms-lease-id");
return Azure::Core::Response<BlobLease>(std::move(response), std::move(pHttpResponse));
}
struct RenewLeaseOptions
{
Azure::Core::Nullable<int32_t> Timeout;
std::string LeaseId;
Azure::Core::Nullable<std::string> IfModifiedSince;
Azure::Core::Nullable<std::string> IfUnmodifiedSince;
Azure::Core::Nullable<std::string> IfMatch;
Azure::Core::Nullable<std::string> IfNoneMatch;
}; // struct RenewLeaseOptions
static Azure::Core::Response<BlobLease> RenewLease(
Azure::Core::Context context,
Azure::Core::Http::HttpPipeline& pipeline,
const std::string& url,
const RenewLeaseOptions& options)
{
unused(options);
auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url);
request.AddHeader("Content-Length", "0");
request.AddHeader("x-ms-version", c_ApiVersion);
if (options.Timeout.HasValue())
{
request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue()));
}
request.AddQueryParameter("comp", "lease");
request.AddHeader("x-ms-lease-action", "renew");
request.AddHeader("x-ms-lease-id", options.LeaseId);
if (options.IfModifiedSince.HasValue())
{
request.AddHeader("If-Modified-Since", options.IfModifiedSince.GetValue());
}
if (options.IfUnmodifiedSince.HasValue())
{
request.AddHeader("If-Unmodified-Since", options.IfUnmodifiedSince.GetValue());
}
if (options.IfMatch.HasValue())
{
request.AddHeader("If-Match", options.IfMatch.GetValue());
}
if (options.IfNoneMatch.HasValue())
{
request.AddHeader("If-None-Match", options.IfNoneMatch.GetValue());
}
auto pHttpResponse = pipeline.Send(context, request);
Azure::Core::Http::RawResponse& httpResponse = *pHttpResponse;
BlobLease response;
auto http_status_code
= static_cast<std::underlying_type<Azure::Core::Http::HttpStatusCode>::type>(
httpResponse.GetStatusCode());
if (!(http_status_code == 200))
{
throw StorageError::CreateFromResponse(context, std::move(pHttpResponse));
}
response.ETag = httpResponse.GetHeaders().at("etag");
response.LastModified = httpResponse.GetHeaders().at("last-modified");
response.LeaseId = httpResponse.GetHeaders().at("x-ms-lease-id");
return Azure::Core::Response<BlobLease>(std::move(response), std::move(pHttpResponse));
}
struct ChangeLeaseOptions
{
Azure::Core::Nullable<int32_t> Timeout;
std::string LeaseId;
std::string ProposedLeaseId;
Azure::Core::Nullable<std::string> IfModifiedSince;
Azure::Core::Nullable<std::string> IfUnmodifiedSince;
Azure::Core::Nullable<std::string> IfMatch;
Azure::Core::Nullable<std::string> IfNoneMatch;
}; // struct ChangeLeaseOptions
static Azure::Core::Response<BlobLease> ChangeLease(
Azure::Core::Context context,
Azure::Core::Http::HttpPipeline& pipeline,
const std::string& url,
const ChangeLeaseOptions& options)
{
unused(options);
auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url);
request.AddHeader("Content-Length", "0");
request.AddHeader("x-ms-version", c_ApiVersion);
if (options.Timeout.HasValue())
{
request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue()));
}
request.AddQueryParameter("comp", "lease");
request.AddHeader("x-ms-lease-action", "change");
request.AddHeader("x-ms-lease-id", options.LeaseId);
request.AddHeader("x-ms-proposed-lease-id", options.ProposedLeaseId);
if (options.IfModifiedSince.HasValue())
{
request.AddHeader("If-Modified-Since", options.IfModifiedSince.GetValue());
}
if (options.IfUnmodifiedSince.HasValue())
{
request.AddHeader("If-Unmodified-Since", options.IfUnmodifiedSince.GetValue());
}
if (options.IfMatch.HasValue())
{
request.AddHeader("If-Match", options.IfMatch.GetValue());
}
if (options.IfNoneMatch.HasValue())
{
request.AddHeader("If-None-Match", options.IfNoneMatch.GetValue());
}
auto pHttpResponse = pipeline.Send(context, request);
Azure::Core::Http::RawResponse& httpResponse = *pHttpResponse;
BlobLease response;
auto http_status_code
= static_cast<std::underlying_type<Azure::Core::Http::HttpStatusCode>::type>(
httpResponse.GetStatusCode());
if (!(http_status_code == 200))
{
throw StorageError::CreateFromResponse(context, std::move(pHttpResponse));
}
response.ETag = httpResponse.GetHeaders().at("etag");
response.LastModified = httpResponse.GetHeaders().at("last-modified");
response.LeaseId = httpResponse.GetHeaders().at("x-ms-lease-id");
return Azure::Core::Response<BlobLease>(std::move(response), std::move(pHttpResponse));
}
struct ReleaseLeaseOptions
{
Azure::Core::Nullable<int32_t> Timeout;
std::string LeaseId;
Azure::Core::Nullable<std::string> IfModifiedSince;
Azure::Core::Nullable<std::string> IfUnmodifiedSince;
Azure::Core::Nullable<std::string> IfMatch;
Azure::Core::Nullable<std::string> IfNoneMatch;
}; // struct ReleaseLeaseOptions
static Azure::Core::Response<BlobInfo> ReleaseLease(
Azure::Core::Context context,
Azure::Core::Http::HttpPipeline& pipeline,
const std::string& url,
const ReleaseLeaseOptions& options)
{
unused(options);
auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url);
request.AddHeader("Content-Length", "0");
request.AddHeader("x-ms-version", c_ApiVersion);
if (options.Timeout.HasValue())
{
request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue()));
}
request.AddQueryParameter("comp", "lease");
request.AddHeader("x-ms-lease-action", "release");
request.AddHeader("x-ms-lease-id", options.LeaseId);
if (options.IfModifiedSince.HasValue())
{
request.AddHeader("If-Modified-Since", options.IfModifiedSince.GetValue());
}
if (options.IfUnmodifiedSince.HasValue())
{
request.AddHeader("If-Unmodified-Since", options.IfUnmodifiedSince.GetValue());
}
if (options.IfMatch.HasValue())
{
request.AddHeader("If-Match", options.IfMatch.GetValue());
}
if (options.IfNoneMatch.HasValue())
{
request.AddHeader("If-None-Match", options.IfNoneMatch.GetValue());
}
auto pHttpResponse = pipeline.Send(context, request);
Azure::Core::Http::RawResponse& httpResponse = *pHttpResponse;
BlobInfo response;
auto http_status_code
= static_cast<std::underlying_type<Azure::Core::Http::HttpStatusCode>::type>(
httpResponse.GetStatusCode());
if (!(http_status_code == 200))
{
throw StorageError::CreateFromResponse(context, std::move(pHttpResponse));
}
response.ETag = httpResponse.GetHeaders().at("etag");
response.LastModified = httpResponse.GetHeaders().at("last-modified");
auto response_sequence_number_iterator
= httpResponse.GetHeaders().find("x-ms-blob-sequence-number");
if (response_sequence_number_iterator != httpResponse.GetHeaders().end())
{
response.SequenceNumber = std::stoll(response_sequence_number_iterator->second);
}
return Azure::Core::Response<BlobInfo>(std::move(response), std::move(pHttpResponse));
}
struct BreakLeaseOptions
{
Azure::Core::Nullable<int32_t> Timeout;
Azure::Core::Nullable<int32_t> BreakPeriod;
Azure::Core::Nullable<std::string> IfModifiedSince;
Azure::Core::Nullable<std::string> IfUnmodifiedSince;
Azure::Core::Nullable<std::string> IfMatch;
Azure::Core::Nullable<std::string> IfNoneMatch;
}; // struct BreakLeaseOptions
static Azure::Core::Response<BrokenLease> BreakLease(
Azure::Core::Context context,
Azure::Core::Http::HttpPipeline& pipeline,
const std::string& url,
const BreakLeaseOptions& options)
{
unused(options);
auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url);
request.AddHeader("Content-Length", "0");
request.AddHeader("x-ms-version", c_ApiVersion);
if (options.Timeout.HasValue())
{
request.AddQueryParameter("timeout", std::to_string(options.Timeout.GetValue()));
}
request.AddQueryParameter("comp", "lease");
request.AddHeader("x-ms-lease-action", "break");
if (options.BreakPeriod.HasValue())
{
request.AddHeader(
"x-ms-lease-break-period", std::to_string(options.BreakPeriod.GetValue()));
}
if (options.IfModifiedSince.HasValue())
{
request.AddHeader("If-Modified-Since", options.IfModifiedSince.GetValue());
}
if (options.IfUnmodifiedSince.HasValue())
{
request.AddHeader("If-Unmodified-Since", options.IfUnmodifiedSince.GetValue());
}
if (options.IfMatch.HasValue())
{
request.AddHeader("If-Match", options.IfMatch.GetValue());
}
if (options.IfNoneMatch.HasValue())
{
request.AddHeader("If-None-Match", options.IfNoneMatch.GetValue());
}
auto pHttpResponse = pipeline.Send(context, request);
Azure::Core::Http::RawResponse& httpResponse = *pHttpResponse;
BrokenLease response;
auto http_status_code
= static_cast<std::underlying_type<Azure::Core::Http::HttpStatusCode>::type>(
httpResponse.GetStatusCode());
if (!(http_status_code == 202))
{
throw StorageError::CreateFromResponse(context, std::move(pHttpResponse));
}
response.ETag = httpResponse.GetHeaders().at("etag");
response.LastModified = httpResponse.GetHeaders().at("last-modified");
response.LeaseTime = std::stoi(httpResponse.GetHeaders().at("x-ms-lease-time"));
return Azure::Core::Response<BrokenLease>(std::move(response), std::move(pHttpResponse));
}
private:
}; // class Blob

View File

@ -3,8 +3,15 @@
#pragma once
#include <cstdint>
#include <string>
namespace Azure { namespace Storage {
template <class... T> void unused(T&&...) {}
constexpr int32_t c_InfiniteLeaseDuration = -1;
std::string CreateUniqueLeaseId();
}} // namespace Azure::Storage

View File

@ -541,4 +541,77 @@ namespace Azure { namespace Storage { namespace Blobs {
options.Context, *m_pipeline, m_blobUrl.ToString(), protocolLayerOptions);
}
Azure::Core::Response<BlobLease> BlobClient::AcquireLease(
const std::string& proposedLeaseId,
int32_t duration,
const AcquireBlobLeaseOptions& options) const
{
BlobRestClient::Blob::AcquireLeaseOptions protocolLayerOptions;
protocolLayerOptions.ProposedLeaseId = proposedLeaseId;
protocolLayerOptions.LeaseDuration = duration;
protocolLayerOptions.IfModifiedSince = options.IfModifiedSince;
protocolLayerOptions.IfUnmodifiedSince = options.IfUnmodifiedSince;
protocolLayerOptions.IfMatch = options.IfMatch;
protocolLayerOptions.IfNoneMatch = options.IfNoneMatch;
return BlobRestClient::Blob::AcquireLease(
options.Context, *m_pipeline, m_blobUrl.ToString(), protocolLayerOptions);
}
Azure::Core::Response<BlobLease> BlobClient::RenewLease(
const std::string& leaseId,
const RenewBlobLeaseOptions& options) const
{
BlobRestClient::Blob::RenewLeaseOptions protocolLayerOptions;
protocolLayerOptions.LeaseId = leaseId;
protocolLayerOptions.IfModifiedSince = options.IfModifiedSince;
protocolLayerOptions.IfUnmodifiedSince = options.IfUnmodifiedSince;
protocolLayerOptions.IfMatch = options.IfMatch;
protocolLayerOptions.IfNoneMatch = options.IfNoneMatch;
return BlobRestClient::Blob::RenewLease(
options.Context, *m_pipeline, m_blobUrl.ToString(), protocolLayerOptions);
}
Azure::Core::Response<BlobInfo> BlobClient::ReleaseLease(
const std::string& leaseId,
const ReleaseBlobLeaseOptions& options) const
{
BlobRestClient::Blob::ReleaseLeaseOptions protocolLayerOptions;
protocolLayerOptions.LeaseId = leaseId;
protocolLayerOptions.IfModifiedSince = options.IfModifiedSince;
protocolLayerOptions.IfUnmodifiedSince = options.IfUnmodifiedSince;
protocolLayerOptions.IfMatch = options.IfMatch;
protocolLayerOptions.IfNoneMatch = options.IfNoneMatch;
return BlobRestClient::Blob::ReleaseLease(
options.Context, *m_pipeline, m_blobUrl.ToString(), protocolLayerOptions);
}
Azure::Core::Response<BlobLease> BlobClient::ChangeLease(
const std::string& leaseId,
const std::string& proposedLeaseId,
const ChangeBlobLeaseOptions& options) const
{
BlobRestClient::Blob::ChangeLeaseOptions protocolLayerOptions;
protocolLayerOptions.LeaseId = leaseId;
protocolLayerOptions.ProposedLeaseId = proposedLeaseId;
protocolLayerOptions.IfModifiedSince = options.IfModifiedSince;
protocolLayerOptions.IfUnmodifiedSince = options.IfUnmodifiedSince;
protocolLayerOptions.IfMatch = options.IfMatch;
protocolLayerOptions.IfNoneMatch = options.IfNoneMatch;
return BlobRestClient::Blob::ChangeLease(
options.Context, *m_pipeline, m_blobUrl.ToString(), protocolLayerOptions);
}
Azure::Core::Response<BrokenLease> BlobClient::BreakLease(
const BreakBlobLeaseOptions& options) const
{
BlobRestClient::Blob::BreakLeaseOptions protocolLayerOptions;
protocolLayerOptions.BreakPeriod = options.breakPeriod;
protocolLayerOptions.IfModifiedSince = options.IfModifiedSince;
protocolLayerOptions.IfUnmodifiedSince = options.IfUnmodifiedSince;
protocolLayerOptions.IfMatch = options.IfMatch;
protocolLayerOptions.IfNoneMatch = options.IfNoneMatch;
return BlobRestClient::Blob::BreakLease(
options.Context, *m_pipeline, m_blobUrl.ToString(), protocolLayerOptions);
}
}}} // namespace Azure::Storage::Blobs

View File

@ -226,4 +226,67 @@ namespace Azure { namespace Storage { namespace Blobs {
options.Context, *m_pipeline, m_containerUrl.ToString(), protocolLayerOptions);
}
Azure::Core::Response<BlobLease> BlobContainerClient::AcquireLease(
const std::string& proposedLeaseId,
int32_t duration,
const AcquireBlobContainerLeaseOptions& options) const
{
BlobRestClient::Container::AcquireLeaseOptions protocolLayerOptions;
protocolLayerOptions.ProposedLeaseId = proposedLeaseId;
protocolLayerOptions.LeaseDuration = duration;
protocolLayerOptions.IfModifiedSince = options.IfModifiedSince;
protocolLayerOptions.IfUnmodifiedSince = options.IfUnmodifiedSince;
return BlobRestClient::Container::AcquireLease(
options.Context, *m_pipeline, m_containerUrl.ToString(), protocolLayerOptions);
}
Azure::Core::Response<BlobLease> BlobContainerClient::RenewLease(
const std::string& leaseId,
const RenewBlobContainerLeaseOptions& options) const
{
BlobRestClient::Container::RenewLeaseOptions protocolLayerOptions;
protocolLayerOptions.LeaseId = leaseId;
protocolLayerOptions.IfModifiedSince = options.IfModifiedSince;
protocolLayerOptions.IfUnmodifiedSince = options.IfUnmodifiedSince;
return BlobRestClient::Container::RenewLease(
options.Context, *m_pipeline, m_containerUrl.ToString(), protocolLayerOptions);
}
Azure::Core::Response<BlobContainerInfo> BlobContainerClient::ReleaseLease(
const std::string& leaseId,
const ReleaseBlobContainerLeaseOptions& options) const
{
BlobRestClient::Container::ReleaseLeaseOptions protocolLayerOptions;
protocolLayerOptions.LeaseId = leaseId;
protocolLayerOptions.IfModifiedSince = options.IfModifiedSince;
protocolLayerOptions.IfUnmodifiedSince = options.IfUnmodifiedSince;
return BlobRestClient::Container::ReleaseLease(
options.Context, *m_pipeline, m_containerUrl.ToString(), protocolLayerOptions);
}
Azure::Core::Response<BlobLease> BlobContainerClient::ChangeLease(
const std::string& leaseId,
const std::string& proposedLeaseId,
const ChangeBlobContainerLeaseOptions& options) const
{
BlobRestClient::Container::ChangeLeaseOptions protocolLayerOptions;
protocolLayerOptions.LeaseId = leaseId;
protocolLayerOptions.ProposedLeaseId = proposedLeaseId;
protocolLayerOptions.IfModifiedSince = options.IfModifiedSince;
protocolLayerOptions.IfUnmodifiedSince = options.IfUnmodifiedSince;
return BlobRestClient::Container::ChangeLease(
options.Context, *m_pipeline, m_containerUrl.ToString(), protocolLayerOptions);
}
Azure::Core::Response<BrokenLease> BlobContainerClient::BreakLease(
const BreakBlobContainerLeaseOptions& options) const
{
BlobRestClient::Container::BreakLeaseOptions protocolLayerOptions;
protocolLayerOptions.BreakPeriod = options.breakPeriod;
protocolLayerOptions.IfModifiedSince = options.IfModifiedSince;
protocolLayerOptions.IfUnmodifiedSince = options.IfUnmodifiedSince;
return BlobRestClient::Container::BreakLease(
options.Context, *m_pipeline, m_containerUrl.ToString(), protocolLayerOptions);
}
}}} // namespace Azure::Storage::Blobs

View File

@ -0,0 +1,35 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#include "common/storage_common.hpp"
#include <random>
namespace Azure { namespace Storage {
std::string CreateUniqueLeaseId()
{
// TODO: return UUID provided by Azure Core once they provide one.
static thread_local std::mt19937_64 random_generator(std::random_device{}());
auto getRandomChar = []() {
const char charset[] = "0123456789abcdef";
std::uniform_int_distribution<size_t> distribution(0, sizeof(charset) - 2);
return charset[distribution(random_generator)];
};
std::string result;
result.reserve(37);
for (int i : {8, 4, 4, 4, 12})
{
for (int j = 0; j < i; ++j)
result += getRandomChar();
result += '-';
}
result.pop_back();
return result;
}
}} // namespace Azure::Storage

View File

@ -272,4 +272,57 @@ namespace Azure { namespace Storage { namespace Test {
container_client.Delete();
}
TEST_F(BlobContainerClientTest, Lease)
{
std::string leaseId1 = CreateUniqueLeaseId();
int32_t leaseDuration = 20;
auto lease = *m_blobContainerClient->AcquireLease(leaseId1, leaseDuration);
EXPECT_FALSE(lease.ETag.empty());
EXPECT_FALSE(lease.LastModified.empty());
EXPECT_EQ(lease.LeaseId, leaseId1);
lease = *m_blobContainerClient->AcquireLease(leaseId1, leaseDuration);
EXPECT_FALSE(lease.ETag.empty());
EXPECT_FALSE(lease.LastModified.empty());
EXPECT_EQ(lease.LeaseId, leaseId1);
auto properties = *m_blobContainerClient->GetProperties();
EXPECT_EQ(properties.LeaseState, Blobs::BlobLeaseState::Leased);
EXPECT_EQ(properties.LeaseStatus, Blobs::BlobLeaseStatus::Locked);
EXPECT_FALSE(properties.LeaseDuration.GetValue().empty());
lease = *m_blobContainerClient->RenewLease(leaseId1);
EXPECT_FALSE(lease.ETag.empty());
EXPECT_FALSE(lease.LastModified.empty());
EXPECT_EQ(lease.LeaseId, leaseId1);
std::string leaseId2 = CreateUniqueLeaseId();
EXPECT_NE(leaseId1, leaseId2);
lease = *m_blobContainerClient->ChangeLease(leaseId1, leaseId2);
EXPECT_FALSE(lease.ETag.empty());
EXPECT_FALSE(lease.LastModified.empty());
EXPECT_EQ(lease.LeaseId, leaseId2);
auto containerInfo = *m_blobContainerClient->ReleaseLease(leaseId2);
EXPECT_FALSE(containerInfo.ETag.empty());
EXPECT_FALSE(containerInfo.LastModified.empty());
lease = *m_blobContainerClient->AcquireLease(CreateUniqueLeaseId(), c_InfiniteLeaseDuration);
properties = *m_blobContainerClient->GetProperties();
EXPECT_FALSE(properties.LeaseDuration.GetValue().empty());
auto brokenLease = *m_blobContainerClient->BreakLease();
EXPECT_FALSE(brokenLease.ETag.empty());
EXPECT_FALSE(brokenLease.LastModified.empty());
EXPECT_EQ(brokenLease.LeaseTime, 0);
lease = *m_blobContainerClient->AcquireLease(CreateUniqueLeaseId(), leaseDuration);
brokenLease = *m_blobContainerClient->BreakLease();
EXPECT_FALSE(brokenLease.ETag.empty());
EXPECT_FALSE(brokenLease.LastModified.empty());
EXPECT_NE(brokenLease.LeaseTime, 0);
Blobs::BreakBlobContainerLeaseOptions options;
options.breakPeriod = 0;
m_blobContainerClient->BreakLease(options);
}
}}} // namespace Azure::Storage::Test

View File

@ -138,4 +138,57 @@ namespace Azure { namespace Storage { namespace Test {
pageBlobClient.StartCopyIncremental(sourceUri.ToString());
}
TEST_F(PageBlobClientTest, Lease)
{
std::string leaseId1 = CreateUniqueLeaseId();
int32_t leaseDuration = 20;
auto lease = *m_pageBlobClient->AcquireLease(leaseId1, leaseDuration);
EXPECT_FALSE(lease.ETag.empty());
EXPECT_FALSE(lease.LastModified.empty());
EXPECT_EQ(lease.LeaseId, leaseId1);
lease = *m_pageBlobClient->AcquireLease(leaseId1, leaseDuration);
EXPECT_FALSE(lease.ETag.empty());
EXPECT_FALSE(lease.LastModified.empty());
EXPECT_EQ(lease.LeaseId, leaseId1);
auto properties = *m_pageBlobClient->GetProperties();
EXPECT_EQ(properties.LeaseState.GetValue(), Blobs::BlobLeaseState::Leased);
EXPECT_EQ(properties.LeaseStatus.GetValue(), Blobs::BlobLeaseStatus::Locked);
EXPECT_FALSE(properties.LeaseDuration.GetValue().empty());
lease = *m_pageBlobClient->RenewLease(leaseId1);
EXPECT_FALSE(lease.ETag.empty());
EXPECT_FALSE(lease.LastModified.empty());
EXPECT_EQ(lease.LeaseId, leaseId1);
std::string leaseId2 = CreateUniqueLeaseId();
EXPECT_NE(leaseId1, leaseId2);
lease = *m_pageBlobClient->ChangeLease(leaseId1, leaseId2);
EXPECT_FALSE(lease.ETag.empty());
EXPECT_FALSE(lease.LastModified.empty());
EXPECT_EQ(lease.LeaseId, leaseId2);
auto blobInfo = *m_pageBlobClient->ReleaseLease(leaseId2);
EXPECT_FALSE(blobInfo.ETag.empty());
EXPECT_FALSE(blobInfo.LastModified.empty());
lease = *m_pageBlobClient->AcquireLease(CreateUniqueLeaseId(), c_InfiniteLeaseDuration);
properties = *m_pageBlobClient->GetProperties();
EXPECT_FALSE(properties.LeaseDuration.GetValue().empty());
auto brokenLease = *m_pageBlobClient->BreakLease();
EXPECT_FALSE(brokenLease.ETag.empty());
EXPECT_FALSE(brokenLease.LastModified.empty());
EXPECT_EQ(brokenLease.LeaseTime, 0);
lease = *m_pageBlobClient->AcquireLease(CreateUniqueLeaseId(), leaseDuration);
brokenLease = *m_pageBlobClient->BreakLease();
EXPECT_FALSE(brokenLease.ETag.empty());
EXPECT_FALSE(brokenLease.LastModified.empty());
EXPECT_NE(brokenLease.LeaseTime, 0);
Blobs::BreakBlobLeaseOptions options;
options.breakPeriod = 0;
m_pageBlobClient->BreakLease(options);
}
}}} // namespace Azure::Storage::Test