Skip to content

Commit

Permalink
Use OperationInternal for all pseudo-LROs
Browse files Browse the repository at this point in the history
Resolves Azure#22912
  • Loading branch information
heaths committed Dec 1, 2021
1 parent e8a47f8 commit 75a3540
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 225 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
<Compile Include="$(AzureCoreSharedSources)ContentTypeUtilities.cs" LinkBase="Shared" />
<Compile Include="$(AzureCoreSharedSources)DiagnosticScope.cs" LinkBase="Shared" />
<Compile Include="$(AzureCoreSharedSources)OperationHelpers.cs" LinkBase="Shared" />
<Compile Include="$(AzureCoreSharedSources)OperationInternal.cs" LinkBase="Shared" />
<Compile Include="$(AzureCoreSharedSources)OperationInternalBase.cs" LinkBase="Shared" />
<Compile Include="$(AzureCoreSharedSources)PageResponseEnumerator.cs" LinkBase="Shared" />
<Compile Include="$(AzureCoreSharedSources)DiagnosticScopeFactory.cs" LinkBase="Shared" />
<Compile Include="$(AzureCoreSharedSources)TaskExtensions.cs" LinkBase="Shared" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,39 @@
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Azure.Core;
using Azure.Core.Pipeline;

namespace Azure.Security.KeyVault.Certificates
{
/// <summary>
/// A long-running operation for <see cref="CertificateClient.StartDeleteCertificate(string, CancellationToken)"/> or <see cref="CertificateClient.StartDeleteCertificateAsync(string, CancellationToken)"/>.
/// </summary>
public class DeleteCertificateOperation : Operation<DeletedCertificate>
public class DeleteCertificateOperation : Operation<DeletedCertificate>, IOperation
{
private static readonly TimeSpan s_defaultPollingInterval = TimeSpan.FromSeconds(2);

private readonly KeyVaultPipeline _pipeline;
private readonly OperationInternal _operationInternal;
private readonly DeletedCertificate _value;
private Response _response;
private bool _completed;

internal DeleteCertificateOperation(KeyVaultPipeline pipeline, Response<DeletedCertificate> response)
{
_pipeline = pipeline;
_value = response.Value ?? throw new InvalidOperationException("The response does not contain a value.");
_response = response.GetRawResponse();
_operationInternal = new(_pipeline.Diagnostics, this, response.GetRawResponse(), nameof(DeleteCertificateOperation), new[]
{
new KeyValuePair<string, string>("secret", _value.Name), // Retained for backward compatibility.
new KeyValuePair<string, string>("certificate", _value.Name),
});

// The recoveryId is only returned if soft-delete is enabled.
// The recoveryId is only returned if soft delete is enabled.
if (_value.RecoveryId is null)
{
_completed = true;
// If soft delete is not enabled, deleting is immediate so set success accordingly.
_operationInternal.SetState(OperationState.Success(response.GetRawResponse()));
}
}

Expand All @@ -50,33 +54,20 @@ protected DeleteCertificateOperation() {}
public override DeletedCertificate Value => _value;

/// <inheritdoc/>
public override bool HasCompleted => _completed;
public override bool HasCompleted => _operationInternal.HasCompleted;

/// <inheritdoc/>
public override bool HasValue => true;

/// <inheritdoc/>
public override Response GetRawResponse() => _response;
public override Response GetRawResponse() => _operationInternal.RawResponse;

/// <inheritdoc/>
public override Response UpdateStatus(CancellationToken cancellationToken = default)
{
if (!_completed)
if (!HasCompleted)
{
using DiagnosticScope scope = _pipeline.CreateScope($"{nameof(DeleteCertificateOperation)}.{nameof(UpdateStatus)}");
scope.AddAttribute("secret", _value.Name);
scope.Start();

try
{
_response = _pipeline.GetResponse(RequestMethod.Get, cancellationToken, CertificateClient.DeletedCertificatesPath, _value.Name);
_completed = CheckCompleted(_response);
}
catch (Exception e)
{
scope.Failed(e);
throw;
}
return _operationInternal.UpdateStatus(cancellationToken);
}

return GetRawResponse();
Expand All @@ -85,22 +76,9 @@ public override Response UpdateStatus(CancellationToken cancellationToken = defa
/// <inheritdoc/>
public override async ValueTask<Response> UpdateStatusAsync(CancellationToken cancellationToken = default)
{
if (!_completed)
if (!HasCompleted)
{
using DiagnosticScope scope = _pipeline.CreateScope($"{nameof(DeleteCertificateOperation)}.{nameof(UpdateStatus)}");
scope.AddAttribute("secret", _value.Name);
scope.Start();

try
{
_response = await _pipeline.GetResponseAsync(RequestMethod.Get, cancellationToken, CertificateClient.DeletedCertificatesPath, _value.Name).ConfigureAwait(false);
_completed = await CheckCompletedAsync(_response).ConfigureAwait(false);
}
catch (Exception e)
{
scope.Failed(e);
throw;
}
return await _operationInternal.UpdateStatusAsync(cancellationToken).ConfigureAwait(false);
}

return GetRawResponse();
Expand All @@ -114,34 +92,27 @@ public override ValueTask<Response<DeletedCertificate>> WaitForCompletionAsync(C
public override ValueTask<Response<DeletedCertificate>> WaitForCompletionAsync(TimeSpan pollingInterval, CancellationToken cancellationToken) =>
this.DefaultWaitForCompletionAsync(pollingInterval, cancellationToken);

private async ValueTask<bool> CheckCompletedAsync(Response response)
async ValueTask<OperationState> IOperation.UpdateStateAsync(bool async, CancellationToken cancellationToken)
{
switch (response.Status)
{
case 200:
case 403: // Access denied but proof the certificate was deleted.
return true;

case 404:
return false;
Response response = async
? await _pipeline.GetResponseAsync(RequestMethod.Get, cancellationToken, CertificateClient.DeletedCertificatesPath, _value.Name).ConfigureAwait(false)
: _pipeline.GetResponse(RequestMethod.Get, cancellationToken, CertificateClient.DeletedCertificatesPath, _value.Name);

default:
throw await _pipeline.Diagnostics.CreateRequestFailedExceptionAsync(response).ConfigureAwait(false);
}
}
private bool CheckCompleted(Response response)
{
switch (response.Status)
{
case 200:
case 403: // Access denied but proof the certificate was deleted.
return true;
return OperationState.Success(response);

case 404:
return false;
return OperationState.Pending(response);

default:
throw _pipeline.Diagnostics.CreateRequestFailedException(response);
RequestFailedException ex = async
? await _pipeline.Diagnostics.CreateRequestFailedExceptionAsync(response).ConfigureAwait(false)
: _pipeline.Diagnostics.CreateRequestFailedException(response);

return OperationState.Failure(response, ex);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,33 @@
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Azure.Core;
using Azure.Core.Pipeline;

namespace Azure.Security.KeyVault.Certificates
{
/// <summary>
/// A long-running operation for <see cref="CertificateClient.StartRecoverDeletedCertificate(string, CancellationToken)"/> or <see cref="CertificateClient.StartRecoverDeletedCertificateAsync(string, CancellationToken)"/>.
/// </summary>
public class RecoverDeletedCertificateOperation : Operation<KeyVaultCertificateWithPolicy>
public class RecoverDeletedCertificateOperation : Operation<KeyVaultCertificateWithPolicy>, IOperation
{
private static readonly TimeSpan s_defaultPollingInterval = TimeSpan.FromSeconds(2);

private readonly KeyVaultPipeline _pipeline;
private readonly OperationInternal _operationInternal;
private readonly KeyVaultCertificateWithPolicy _value;
private Response _response;
private bool _completed;

internal RecoverDeletedCertificateOperation(KeyVaultPipeline pipeline, Response<KeyVaultCertificateWithPolicy> response)
{
_pipeline = pipeline;
_value = response.Value ?? throw new InvalidOperationException("The response does not contain a value.");
_response = response.GetRawResponse();
_operationInternal = new(_pipeline.Diagnostics, this, response.GetRawResponse(), nameof(RecoverDeletedCertificateOperation), new[]
{
new KeyValuePair<string, string>("secret", _value.Name), // Retained for backward compatibility.
new KeyValuePair<string, string>("certificate", _value.Name),
});
}

/// <summary> Initializes a new instance of <see cref="RecoverDeletedCertificateOperation" /> for mocking. </summary>
Expand All @@ -44,33 +47,20 @@ protected RecoverDeletedCertificateOperation() {}
public override KeyVaultCertificateWithPolicy Value => _value;

/// <inheritdoc/>
public override bool HasCompleted => _completed;
public override bool HasCompleted => _operationInternal.HasCompleted;

/// <inheritdoc/>
public override bool HasValue => true;

/// <inheritdoc/>
public override Response GetRawResponse() => _response;
public override Response GetRawResponse() => _operationInternal.RawResponse;

/// <inheritdoc/>
public override Response UpdateStatus(CancellationToken cancellationToken = default)
{
if (!_completed)
if (!HasCompleted)
{
using DiagnosticScope scope = _pipeline.CreateScope($"{nameof(RecoverDeletedCertificateOperation)}.{nameof(UpdateStatus)}");
scope.AddAttribute("secret", _value.Name);
scope.Start();

try
{
_response = _pipeline.GetResponse(RequestMethod.Get, cancellationToken, CertificateClient.CertificatesPath, _value.Name, "/", _value.Properties.Version);
_completed = CheckCompleted(_response);
}
catch (Exception e)
{
scope.Failed(e);
throw;
}
return _operationInternal.UpdateStatus(cancellationToken);
}

return GetRawResponse();
Expand All @@ -79,22 +69,9 @@ public override Response UpdateStatus(CancellationToken cancellationToken = defa
/// <inheritdoc/>
public override async ValueTask<Response> UpdateStatusAsync(CancellationToken cancellationToken = default)
{
if (!_completed)
if (!HasCompleted)
{
using DiagnosticScope scope = _pipeline.CreateScope($"{nameof(RecoverDeletedCertificateOperation)}.{nameof(UpdateStatus)}");
scope.AddAttribute("secret", _value.Name);
scope.Start();

try
{
_response = await _pipeline.GetResponseAsync(RequestMethod.Get, cancellationToken, CertificateClient.CertificatesPath, _value.Name, "/", _value.Properties.Version).ConfigureAwait(false);
_completed = await CheckCompletedAsync(_response).ConfigureAwait(false);
}
catch (Exception e)
{
scope.Failed(e);
throw;
}
return await _operationInternal.UpdateStatusAsync(cancellationToken).ConfigureAwait(false);
}

return GetRawResponse();
Expand All @@ -108,34 +85,27 @@ public override ValueTask<Response<KeyVaultCertificateWithPolicy>> WaitForComple
public override ValueTask<Response<KeyVaultCertificateWithPolicy>> WaitForCompletionAsync(TimeSpan pollingInterval, CancellationToken cancellationToken) =>
this.DefaultWaitForCompletionAsync(pollingInterval, cancellationToken);

private async ValueTask<bool> CheckCompletedAsync(Response response)
async ValueTask<OperationState> IOperation.UpdateStateAsync(bool async, CancellationToken cancellationToken)
{
switch (response.Status)
{
case 200:
case 403: // Access denied but proof the certificate was recovered.
return true;

case 404:
return false;
Response response = async
? await _pipeline.GetResponseAsync(RequestMethod.Get, cancellationToken, CertificateClient.CertificatesPath, _value.Name, "/", _value.Properties.Version).ConfigureAwait(false)
: _pipeline.GetResponse(RequestMethod.Get, cancellationToken, CertificateClient.CertificatesPath, _value.Name, "/", _value.Properties.Version);

default:
throw await _pipeline.Diagnostics.CreateRequestFailedExceptionAsync(response).ConfigureAwait(false);
}
}
private bool CheckCompleted(Response response)
{
switch (response.Status)
{
case 200:
case 403: // Access denied but proof the certificate was recovered.
return true;
return OperationState.Success(response);

case 404:
return false;
return OperationState.Pending(response);

default:
throw _pipeline.Diagnostics.CreateRequestFailedException(response);
RequestFailedException ex = async
? await _pipeline.Diagnostics.CreateRequestFailedExceptionAsync(response).ConfigureAwait(false)
: _pipeline.Diagnostics.CreateRequestFailedException(response);

return OperationState.Failure(response, ex);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
<Compile Include="$(AzureCoreSharedSources)PageResponseEnumerator.cs" LinkBase="Shared" />
<Compile Include="$(AzureCoreSharedSources)DiagnosticScopeFactory.cs" LinkBase="Shared" />
<Compile Include="$(AzureCoreSharedSources)OperationHelpers.cs" LinkBase="Shared" />
<Compile Include="$(AzureCoreSharedSources)OperationInternal.cs" LinkBase="Shared" />
<Compile Include="$(AzureCoreSharedSources)OperationInternalBase.cs" LinkBase="Shared" />
<Compile Include="$(AzureCoreSharedSources)TaskExtensions.cs" LinkBase="Shared" />
</ItemGroup>

Expand Down
Loading

0 comments on commit 75a3540

Please sign in to comment.