diff --git a/sdk/storage/CMakeLists.txt b/sdk/storage/CMakeLists.txt index a4a540f17..e76600259 100644 --- a/sdk/storage/CMakeLists.txt +++ b/sdk/storage/CMakeLists.txt @@ -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 diff --git a/sdk/storage/inc/blobs/blob_client.hpp b/sdk/storage/inc/blobs/blob_client.hpp index f47054f04..7b1070735 100644 --- a/sdk/storage/inc/blobs/blob_client.hpp +++ b/sdk/storage/inc/blobs/blob_client.hpp @@ -284,6 +284,76 @@ namespace Azure { namespace Storage { namespace Blobs { Azure::Core::Response 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 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 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 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 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 BreakLease( + const BreakBlobLeaseOptions& options = BreakBlobLeaseOptions()) const; + protected: UriBuilder m_blobUrl; std::shared_ptr m_pipeline; diff --git a/sdk/storage/inc/blobs/blob_container_client.hpp b/sdk/storage/inc/blobs/blob_container_client.hpp index ca915c831..a84c8f2ae 100644 --- a/sdk/storage/inc/blobs/blob_container_client.hpp +++ b/sdk/storage/inc/blobs/blob_container_client.hpp @@ -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 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 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 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 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 BreakLease( + const BreakBlobContainerLeaseOptions& options = BreakBlobContainerLeaseOptions()) const; + private: UriBuilder m_containerUrl; std::shared_ptr m_pipeline; diff --git a/sdk/storage/inc/blobs/blob_options.hpp b/sdk/storage/inc/blobs/blob_options.hpp index 7148f36aa..b3a5db686 100644 --- a/sdk/storage/inc/blobs/blob_options.hpp +++ b/sdk/storage/inc/blobs/blob_options.hpp @@ -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 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 breakPeriod; + }; + /** * @brief Optional parameters for BlockBlobClient::Upload. */ diff --git a/sdk/storage/inc/blobs/protocol/blob_rest_client.hpp b/sdk/storage/inc/blobs/protocol/blob_rest_client.hpp index 6d12454a0..e5852b79c 100644 --- a/sdk/storage/inc/blobs/protocol/blob_rest_client.hpp +++ b/sdk/storage/inc/blobs/protocol/blob_rest_client.hpp @@ -382,6 +382,13 @@ namespace Azure { namespace Storage { namespace Blobs { Azure::Core::Nullable 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 Timeout; + int32_t LeaseDuration = -1; + Azure::Core::Nullable ProposedLeaseId; + Azure::Core::Nullable IfModifiedSince; + Azure::Core::Nullable IfUnmodifiedSince; + }; // struct AcquireLeaseOptions + + static Azure::Core::Response 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::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(std::move(response), std::move(pHttpResponse)); + } + + struct RenewLeaseOptions + { + Azure::Core::Nullable Timeout; + std::string LeaseId; + Azure::Core::Nullable IfModifiedSince; + Azure::Core::Nullable IfUnmodifiedSince; + }; // struct RenewLeaseOptions + + static Azure::Core::Response 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::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(std::move(response), std::move(pHttpResponse)); + } + + struct ChangeLeaseOptions + { + Azure::Core::Nullable Timeout; + std::string LeaseId; + std::string ProposedLeaseId; + Azure::Core::Nullable IfModifiedSince; + Azure::Core::Nullable IfUnmodifiedSince; + }; // struct ChangeLeaseOptions + + static Azure::Core::Response 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::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(std::move(response), std::move(pHttpResponse)); + } + + struct ReleaseLeaseOptions + { + Azure::Core::Nullable Timeout; + std::string LeaseId; + Azure::Core::Nullable IfModifiedSince; + Azure::Core::Nullable IfUnmodifiedSince; + }; // struct ReleaseLeaseOptions + + static Azure::Core::Response 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::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( + std::move(response), std::move(pHttpResponse)); + } + + struct BreakLeaseOptions + { + Azure::Core::Nullable Timeout; + Azure::Core::Nullable BreakPeriod; + Azure::Core::Nullable IfModifiedSince; + Azure::Core::Nullable IfUnmodifiedSince; + }; // struct BreakLeaseOptions + + static Azure::Core::Response 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::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(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 Timeout; + int32_t LeaseDuration = -1; + Azure::Core::Nullable ProposedLeaseId; + Azure::Core::Nullable IfModifiedSince; + Azure::Core::Nullable IfUnmodifiedSince; + Azure::Core::Nullable IfMatch; + Azure::Core::Nullable IfNoneMatch; + }; // struct AcquireLeaseOptions + + static Azure::Core::Response 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::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(std::move(response), std::move(pHttpResponse)); + } + + struct RenewLeaseOptions + { + Azure::Core::Nullable Timeout; + std::string LeaseId; + Azure::Core::Nullable IfModifiedSince; + Azure::Core::Nullable IfUnmodifiedSince; + Azure::Core::Nullable IfMatch; + Azure::Core::Nullable IfNoneMatch; + }; // struct RenewLeaseOptions + + static Azure::Core::Response 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::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(std::move(response), std::move(pHttpResponse)); + } + + struct ChangeLeaseOptions + { + Azure::Core::Nullable Timeout; + std::string LeaseId; + std::string ProposedLeaseId; + Azure::Core::Nullable IfModifiedSince; + Azure::Core::Nullable IfUnmodifiedSince; + Azure::Core::Nullable IfMatch; + Azure::Core::Nullable IfNoneMatch; + }; // struct ChangeLeaseOptions + + static Azure::Core::Response 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::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(std::move(response), std::move(pHttpResponse)); + } + + struct ReleaseLeaseOptions + { + Azure::Core::Nullable Timeout; + std::string LeaseId; + Azure::Core::Nullable IfModifiedSince; + Azure::Core::Nullable IfUnmodifiedSince; + Azure::Core::Nullable IfMatch; + Azure::Core::Nullable IfNoneMatch; + }; // struct ReleaseLeaseOptions + + static Azure::Core::Response 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::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(std::move(response), std::move(pHttpResponse)); + } + + struct BreakLeaseOptions + { + Azure::Core::Nullable Timeout; + Azure::Core::Nullable BreakPeriod; + Azure::Core::Nullable IfModifiedSince; + Azure::Core::Nullable IfUnmodifiedSince; + Azure::Core::Nullable IfMatch; + Azure::Core::Nullable IfNoneMatch; + }; // struct BreakLeaseOptions + + static Azure::Core::Response 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::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(std::move(response), std::move(pHttpResponse)); + } + private: }; // class Blob diff --git a/sdk/storage/inc/common/storage_common.hpp b/sdk/storage/inc/common/storage_common.hpp index fefaab168..218eace71 100644 --- a/sdk/storage/inc/common/storage_common.hpp +++ b/sdk/storage/inc/common/storage_common.hpp @@ -3,8 +3,15 @@ #pragma once +#include +#include + namespace Azure { namespace Storage { template void unused(T&&...) {} + constexpr int32_t c_InfiniteLeaseDuration = -1; + + std::string CreateUniqueLeaseId(); + }} // namespace Azure::Storage diff --git a/sdk/storage/src/blobs/blob_client.cpp b/sdk/storage/src/blobs/blob_client.cpp index 53c74797a..ef60714b2 100644 --- a/sdk/storage/src/blobs/blob_client.cpp +++ b/sdk/storage/src/blobs/blob_client.cpp @@ -541,4 +541,77 @@ namespace Azure { namespace Storage { namespace Blobs { options.Context, *m_pipeline, m_blobUrl.ToString(), protocolLayerOptions); } + Azure::Core::Response 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 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 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 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 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 diff --git a/sdk/storage/src/blobs/blob_container_client.cpp b/sdk/storage/src/blobs/blob_container_client.cpp index 9151d35e6..4063ee332 100644 --- a/sdk/storage/src/blobs/blob_container_client.cpp +++ b/sdk/storage/src/blobs/blob_container_client.cpp @@ -226,4 +226,67 @@ namespace Azure { namespace Storage { namespace Blobs { options.Context, *m_pipeline, m_containerUrl.ToString(), protocolLayerOptions); } + Azure::Core::Response 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 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 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 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 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 diff --git a/sdk/storage/src/common/storage_common.cpp b/sdk/storage/src/common/storage_common.cpp new file mode 100644 index 000000000..aba978dba --- /dev/null +++ b/sdk/storage/src/common/storage_common.cpp @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#include "common/storage_common.hpp" + +#include + +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 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 diff --git a/sdk/storage/test/blobs/blob_container_client_test.cpp b/sdk/storage/test/blobs/blob_container_client_test.cpp index 69de4c19c..e95f6242c 100644 --- a/sdk/storage/test/blobs/blob_container_client_test.cpp +++ b/sdk/storage/test/blobs/blob_container_client_test.cpp @@ -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 diff --git a/sdk/storage/test/blobs/page_blob_client_test.cpp b/sdk/storage/test/blobs/page_blob_client_test.cpp index 9507e7ac7..81423df2b 100644 --- a/sdk/storage/test/blobs/page_blob_client_test.cpp +++ b/sdk/storage/test/blobs/page_blob_client_test.cpp @@ -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