diff --git a/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure.Tests/LROTests/LongRunningOperationsTest.cs b/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure.Tests/LROTests/LongRunningOperationsTest.cs
index 46bcb3b24af0..d121ea77b394 100644
--- a/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure.Tests/LROTests/LongRunningOperationsTest.cs
+++ b/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure.Tests/LROTests/LongRunningOperationsTest.cs
@@ -488,47 +488,7 @@ public void TestCreateOrUpdateFailedStatus()
}
- ///
- /// Test
- ///
- [Fact]
- public void TestCreateOrUpdateErrorHandling()
- {
- var tokenCredentials = new TokenCredentials("123", "abc");
- var handler = new PlaybackTestHandler(LROResponse.MockCreateOrUpdateWithImmediateServerError());
- var fakeClient = new RedisManagementClient(tokenCredentials, handler);
- fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0;
- try
- {
- fakeClient.RedisOperations.CreateOrUpdate("rg", "redis", new RedisCreateOrUpdateParameters(), "1234");
- Assert.False(true, "Expected exception was not thrown.");
- }
- catch(CloudException ex)
- {
- Assert.Equal("The provided database ‘foo’ has an invalid username.", ex.Message);
- }
- }
-
- ///
- /// Test
- ///
- [Fact]
- public void TestCreateOrUpdateNoErrorBody()
- {
- var tokenCredentials = new TokenCredentials("123", "abc");
- var handler = new PlaybackTestHandler(LROResponse.MockCreateOrUpdateWithNoErrorBody());
- var fakeClient = new RedisManagementClient(tokenCredentials, handler);
- fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0;
- try
- {
- fakeClient.RedisOperations.CreateOrUpdate("rg", "redis", new RedisCreateOrUpdateParameters(), "1234");
- Assert.False(true, "Expected exception was not thrown.");
- }
- catch (CloudException ex)
- {
- Assert.Equal(HttpStatusCode.InternalServerError, ex.Response.StatusCode);
- }
- }
+
///
@@ -573,28 +533,6 @@ public void TestDeleteWithLocationHeader()
Assert.Equal(2, handler.Requests.Count);
}
- ///
- /// Test
- ///
- [Fact]
- public void TestDeleteWithLocationHeaderErrorHandling()
- {
- var tokenCredentials = new TokenCredentials("123", "abc");
- var handler = new PlaybackTestHandler(LROResponse.MockDeleteWithLocationHeaderError());
- var fakeClient = new RedisManagementClient(tokenCredentials, handler);
- fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0;
-
- try
- {
- fakeClient.RedisOperations.Delete("rg", "redis", "1234");
- Assert.False(true, "Expected exception was not thrown.");
- }
- catch (CloudException ex)
- {
- Assert.Null(ex.Body);
- }
- }
-
///
/// Test
///
@@ -610,66 +548,6 @@ public void TestDeleteWithLocationHeaderErrorHandlingSecondTime()
Assert.Equal("Long running operation failed with status 'InternalServerError'.", ex.Message);
}
- ///
- /// Test
- ///
- [Fact]
- public void TestLROAsynOperationFailureWith200()
- {
- var tokenCredentials = new TokenCredentials("123", "abc");
- var handler = new PlaybackTestHandler(LROResponse.MockLROAsyncOperationFailedWith200());
- var fakeClient = new RedisManagementClient(tokenCredentials, handler);
- fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0;
- try
- {
- var foo = fakeClient.RedisOperations.CreateOrUpdate("rg", "redis", new RedisCreateOrUpdateParameters(), "1234");
- }
- catch(Exception ex)
- {
- Assert.Contains("DeploymentDocument", ex.Message);
- }
- }
-
- ///
- /// Test
- ///
- [Fact]
- public void TestLROWithLocationHeaderFailureWith200()
- {
- var tokenCredentials = new TokenCredentials("123", "abc");
- var handler = new PlaybackTestHandler(LROResponse.MockLROLocationHeaderFailedWith200());
- var fakeClient = new RedisManagementClient(tokenCredentials, handler);
- fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0;
- try
- {
- var foo = fakeClient.RedisOperations.CreateOrUpdate("rg", "redis", new RedisCreateOrUpdateParameters(), "1234");
- }
- catch (Exception ex)
- {
- // If the message changes in the response, this assert will also have to be updated.
- Assert.Contains("DeploymentDocument", ex.Message);
- }
- }
-
- ///
- /// Test
- ///
- [Fact]
- public void TestLROPUTWithCanceledState()
- {
- var tokenCredentials = new TokenCredentials("123", "abc");
- var handler = new PlaybackTestHandler(LROResponse.MockLROPUTWithCanceledStateResponse());
- var fakeClient = new RedisManagementClient(tokenCredentials, handler);
- fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0;
- try
- {
- var foo = fakeClient.RedisOperations.CreateOrUpdate("rg", "redis", new RedisCreateOrUpdateParameters(), "1234");
- }
- catch (Exception ex)
- {
- Assert.Contains("preempted", ex.Message);
- }
- }
///
/// Test
@@ -876,6 +754,9 @@ public void TestPatchWithLocationHeader()
///
public class LRO_FailedTests
{
+ ///
+ ///
+ ///
[Fact /*(Skip = "Potential scenario that will have to be supported")*/]
public void TestLROAsynOperationFailureWith200()
{
@@ -883,13 +764,126 @@ public void TestLROAsynOperationFailureWith200()
var handler = new PlaybackTestHandler(LROFailedResponses.MockLROAsyncOperationFailedOnlyStatus());
var fakeClient = new RedisManagementClient(tokenCredentials, handler);
fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0;
+ Assert.Throws(() =>
+ {
+ try
+ {
+ fakeClient.RedisOperations.CreateOrUpdate("rg", "redis", new RedisCreateOrUpdateParameters(), "1234");
+ }
+ catch (Exception ex)
+ {
+ Assert.Contains("Unable to deserilize body", ex.Message);
+ throw ex;
+ }
+ });
+ }
+
+ ///
+ /// Test
+ ///
+ [Fact]
+ public void TestCreateOrUpdateErrorHandling()
+ {
+ var tokenCredentials = new TokenCredentials("123", "abc");
+ var handler = new PlaybackTestHandler(LROResponse.MockCreateOrUpdateWithImmediateServerError());
+ var fakeClient = new RedisManagementClient(tokenCredentials, handler);
+ fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0;
+ try
+ {
+ fakeClient.RedisOperations.CreateOrUpdate("rg", "redis", new RedisCreateOrUpdateParameters(), "1234");
+ Assert.False(true, "Expected exception was not thrown.");
+ }
+ catch (CloudException ex)
+ {
+ Assert.Equal("The provided database ‘foo’ has an invalid username.", ex.Message);
+ }
+ }
+
+ ///
+ /// Test
+ ///
+ [Fact]
+ public void TestCreateOrUpdateNoErrorBody()
+ {
+ var tokenCredentials = new TokenCredentials("123", "abc");
+ var handler = new PlaybackTestHandler(LROResponse.MockCreateOrUpdateWithNoErrorBody());
+ var fakeClient = new RedisManagementClient(tokenCredentials, handler);
+ fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0;
+ try
+ {
+ fakeClient.RedisOperations.CreateOrUpdate("rg", "redis", new RedisCreateOrUpdateParameters(), "1234");
+ Assert.False(true, "Expected exception was not thrown.");
+ }
+ catch (CloudException ex)
+ {
+ Assert.Equal(HttpStatusCode.InternalServerError, ex.Response.StatusCode);
+ }
+ }
+
+ ///
+ /// Test
+ ///
+ [Fact]
+ public void TestDeleteWithLocationHeaderErrorHandling()
+ {
+ var tokenCredentials = new TokenCredentials("123", "abc");
+ var handler = new PlaybackTestHandler(LROResponse.MockDeleteWithLocationHeaderError());
+ var fakeClient = new RedisManagementClient(tokenCredentials, handler);
+ fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0;
+
+ try
+ {
+ fakeClient.RedisOperations.Delete("rg", "redis", "1234");
+ Assert.False(true, "Expected exception was not thrown.");
+ }
+ catch (CloudException ex)
+ {
+ Assert.Null(ex.Body);
+ }
+ }
+
+ ///
+ /// Test
+ ///
+ [Fact]
+ public void TestLROWithLocationHeaderFailureWith200()
+ {
+ var tokenCredentials = new TokenCredentials("123", "abc");
+ var handler = new PlaybackTestHandler(LROResponse.MockLROLocationHeaderFailedWith200());
+ var fakeClient = new RedisManagementClient(tokenCredentials, handler);
+ fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0;
+
+ Assert.Throws(() =>
+ {
+ try
+ {
+ fakeClient.RedisOperations.CreateOrUpdate("rg", "redis", new RedisCreateOrUpdateParameters(), "1234");
+ }
+ catch(Exception ex)
+ {
+ Assert.Contains("DeploymentDocument", ex.Message);
+ throw ex;
+ }
+ });
+ }
+
+ ///
+ /// Test
+ ///
+ [Fact]
+ public void TestLROPUTWithCanceledState()
+ {
+ var tokenCredentials = new TokenCredentials("123", "abc");
+ var handler = new PlaybackTestHandler(LROResponse.MockLROPUTWithCanceledStateResponse());
+ var fakeClient = new RedisManagementClient(tokenCredentials, handler);
+ fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0;
try
{
var foo = fakeClient.RedisOperations.CreateOrUpdate("rg", "redis", new RedisCreateOrUpdateParameters(), "1234");
}
catch (Exception ex)
{
- Assert.Contains("Unable to deserilize body", ex.Message);
+ Assert.Contains("preempted", ex.Message);
}
}
}
@@ -928,8 +922,5 @@ public void TestPUT_WithMultipleHeaders()
Assert.Equal(HttpMethod.Get, handler.Requests[3].Method);
Assert.Equal("https://management.azure.com/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Cache/Redis/redis", handler.Requests[3].RequestUri.ToString());
}
-
-
-
}
}
diff --git a/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure/LRO/Base/AzureLRO.cs b/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure/LRO/Base/AzureLRO.cs
index b8b5f64e12f7..728697c81404 100644
--- a/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure/LRO/Base/AzureLRO.cs
+++ b/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure/LRO/Base/AzureLRO.cs
@@ -67,7 +67,7 @@ public virtual async Task BeginLROAsync()
InitializeAsyncHeadersToUse();
await StartPollingAsync();
await PostPollingAsync();
- CheckForErrors();
+ CheckFinalErrors();
IsLROTaskCompleted = true;
}
@@ -88,7 +88,20 @@ public virtual async Task
#endregion
#region Protected functions
-
+
+ ///
+ /// Check for errors at the end of LRO operation
+ /// Last chance to check any final errors
+ ///
+ protected virtual void CheckFinalErrors()
+ {
+ if (!string.IsNullOrEmpty(CurrentPollingState.LastSerializationExceptionMessage))
+ {
+ throw new CloudException(string.Format(Resources.BodyDeserializationError, CurrentPollingState.LastSerializationExceptionMessage));
+ }
+ }
+
+
///
/// Does basic validation on initial response from RP, prior to start LRO process
///
@@ -181,11 +194,9 @@ protected virtual void UpdatePollingState()
#region Check provisionState
CurrentPollingState.CurrentStatusCode = CurrentPollingState.Response.StatusCode;
- if (!string.IsNullOrEmpty(CurrentPollingState.AsyncOperationResponseBody?.Status)
- &&
- (!string.IsNullOrEmpty(CurrentPollingState.AzureAsyncOperationHeaderLink)))
+ if (IsAzureAsyncOperationResponseStateValid() == true)
{
- CurrentPollingState.Status = CurrentPollingState.AsyncOperationResponseBody.Status;
+ CurrentPollingState.Status = GetAzureAsyncResponseState();
}
else
{
@@ -289,6 +300,85 @@ protected virtual string GetValidAbsoluteUri(string url, bool throwForInvalidUri
return absoluteUri;
}
+
+ #endregion
+
+ #region Private functions
+ ///
+ /// Get Valid status
+ /// There are cases where there is an error sent from the service and in that case, the status should be one of the valid FailedStatuses
+ /// But there are cases where there is a customized error sent by service and they do not fall under Failed/Success statuses, in that case we fall back on response status
+ ///
+ /// e.g. The response status is OK, but the error body has the status as "TestFailed" (which do not fall under valid failed status, so we fall back to OK)
+ ///
+ ///
+ private string GetAzureAsyncResponseState()
+ {
+ string validStatus = string.Empty;
+ if (!string.IsNullOrEmpty(CurrentPollingState.AsyncOperationResponseBody?.Status))
+ {
+ if (AzureAsyncOperation.FailedStatuses.Any(
+ s => s.Equals(CurrentPollingState.AsyncOperationResponseBody.Status, StringComparison.OrdinalIgnoreCase)))
+ {
+ validStatus = CurrentPollingState.AsyncOperationResponseBody.Status;
+ }
+ else if (AzureAsyncOperation.TerminalStatuses.Any(s => s.Equals(CurrentPollingState.AsyncOperationResponseBody.Status, StringComparison.OrdinalIgnoreCase)))
+ {
+ validStatus = CurrentPollingState.AsyncOperationResponseBody.Status;
+ }
+ else if (string.IsNullOrEmpty(validStatus))
+ {
+ validStatus = CurrentPollingState.Response.StatusCode.ToString();
+ }
+ }
+
+ return validStatus;
+ }
+
+ ///
+ /// This function determines if you are running your polling under Azure-Async header or if the response status falls under terminal/failed status
+ ///
+ ///
+ private bool IsAzureAsyncOperationResponseStateValid()
+ {
+ if (CurrentPollingState?.AsyncOperationResponseBody != null && !string.IsNullOrEmpty(CurrentPollingState.AsyncOperationResponseBody?.Status))
+ {
+ if (AzureAsyncOperation.FailedStatuses.Any(
+ s => s.Equals(CurrentPollingState.AsyncOperationResponseBody.Status, StringComparison.OrdinalIgnoreCase)))
+ {
+ return true;
+ }
+ else if (AzureAsyncOperation.TerminalStatuses.Any(s => s.Equals(CurrentPollingState.AsyncOperationResponseBody.Status, StringComparison.OrdinalIgnoreCase)))
+ {
+ return true;
+ }
+ else if(IsUriEqual(CurrentPollingState.PollingUrlToUse, CurrentPollingState.AzureAsyncOperationHeaderLink))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// Check URI for equality including differences in trailing slash and compare case insensitive
+ ///
+ /// Url
+ /// Url to compare against
+ ///
+ private bool IsUriEqual(string leftUrl, string rightUrl)
+ {
+ if (string.IsNullOrEmpty(leftUrl)) return false;
+ if (string.IsNullOrEmpty(rightUrl)) return false;
+
+ Uri left = new Uri(leftUrl);
+ Uri right = new Uri(rightUrl);
+
+ int result = Uri.Compare(left, right, UriComponents.Fragment, UriFormat.SafeUnescaped, StringComparison.OrdinalIgnoreCase);
+ return (result == 0);
+ }
+
#endregion
}
}
diff --git a/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure/LRO/LROPollState.cs b/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure/LRO/LROPollState.cs
index 7a28598634f1..cdf19684e804 100644
--- a/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure/LRO/LROPollState.cs
+++ b/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure/LRO/LROPollState.cs
@@ -243,6 +243,14 @@ internal async Task> GetRawAsync(
try
{
body = JObject.Parse(responseContent);
+
+ // We only keep last serialization expcetion that occured in the last LRO poll cycle
+ // even if we got serialziation exception in the last iteration but the next response does not result
+ //
+ if(!string.IsNullOrEmpty(this.LastSerializationExceptionMessage))
+ {
+ this.LastSerializationExceptionMessage = string.Empty;
+ }
}
catch(Exception ex)
{