From 0d132e8d949ac66e7071cc902b82bef100b42791 Mon Sep 17 00:00:00 2001 From: Abhijeet Date: Thu, 24 Aug 2017 15:07:33 -0700 Subject: [PATCH] Retry-After header is honored for LRO polling. Added tests. Added LRO Patch scenario test. Cleaned up PollingState: Obsolete DelayInMillisecond propety, introduced DelayBetweenPolling property that returns TimeSpan. Bumped version. (#3621) --- .../LROOpertionTestResponses.cs | 124 +++++++++++++++++ .../LongRunningOperationsTest.cs | 122 +++++++++++++---- ...soft.Rest.ClientRuntime.Azure.Tests.csproj | 9 +- .../Sample/RedisManagementClient.cs | 129 ++++++++++++++---- .../AzureClientExtensions.cs | 30 ++-- .../AzureLROOperationExtensions.cs | 1 - .../Microsoft.Rest.ClientRuntime.Azure.csproj | 2 +- .../ClientRuntime.Azure/PollingState.cs | 92 +++++++++++-- .../Properties/AssemblyInfo.cs | 2 +- 9 files changed, 422 insertions(+), 89 deletions(-) diff --git a/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure.Tests/LROOpertionTestResponses.cs b/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure.Tests/LROOpertionTestResponses.cs index b6288ec25ffe..00cea42a16e2 100644 --- a/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure.Tests/LROOpertionTestResponses.cs +++ b/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure.Tests/LROOpertionTestResponses.cs @@ -1107,5 +1107,129 @@ static internal IEnumerable MockDeleteWithRetryAfterTwoTrie yield return response2; } + static internal IEnumerable MockPatchWithRetryAfterTwoTries() + { + var response1 = new HttpResponseMessage(HttpStatusCode.Accepted) + { + Content = new StringContent(@"") + }; + response1.Headers.Add("Location", "http://custom/location/status"); + response1.Headers.Add("Retry-After", "3"); + + yield return response1; + + var response2 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent("{ \"properties\": { }, \"id\": \"100\", \"name\": \"foo\" }") + }; + + yield return response2; + } + + static internal IEnumerable MockCreateOrUpdateWithDifferentRetryAfterValues() + { + var response1 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(@" + { + ""location"": ""North US"", + ""tags"": { + ""key1"": ""value 1"", + ""key2"": ""value 2"" + }, + + ""properties"": { + ""provisioningState"": ""InProgerss"", + ""comment"": ""Resource defined structure"" + } + }") + }; + response1.Headers.Add("Azure-AsyncOperation", "http://custom/status"); + response1.Headers.Add("Retry-After", "2"); + + yield return response1; + + var response2 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(@" + { + ""status"" : ""InProgerss"", + ""properties"": { + ""provisioningState"": ""InProgerss"", + ""comment"": ""Resource getting created"" + } + }") + }; + response2.Headers.Add("Retry-After", "5"); + + yield return response2; + + var response3 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(@" + { + ""status"" : ""Succeeded"", + ""properties"": { + ""id"": ""100"", + ""name"": ""foo"" + } + }") + }; + + yield return response3; + + var response4 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(@" + { + ""status"" : ""Succeeded"", + ""properties"": { + ""id"": ""100"", + ""name"": ""foo"" + } + }") + }; + + yield return response4; + } + + static internal IEnumerable MockCreateWithRetryAfterDefaultMin() + { + var response1 = new HttpResponseMessage(HttpStatusCode.Accepted) + { + Content = new StringContent(@"") + }; + response1.Headers.Add("Location", "http://custom/location/status"); + response1.Headers.Add("Retry-After", "0"); + + yield return response1; + + var response2 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent("{ \"properties\": { }, \"id\": \"100\", \"name\": \"foo\" }") + }; + + yield return response2; + } + + static internal IEnumerable MockCreateWithRetryAfterDefaultMax() + { + var response1 = new HttpResponseMessage(HttpStatusCode.Accepted) + { + Content = new StringContent(@"") + }; + response1.Headers.Add("Location", "http://custom/location/status"); + response1.Headers.Add("Retry-After", "50"); + + yield return response1; + + var response2 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent("{ \"properties\": { }, \"id\": \"100\", \"name\": \"foo\" }") + }; + + yield return response2; + } + } } diff --git a/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure.Tests/LongRunningOperationsTest.cs b/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure.Tests/LongRunningOperationsTest.cs index 8328c693c41a..822fb7240bc7 100644 --- a/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure.Tests/LongRunningOperationsTest.cs +++ b/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure.Tests/LongRunningOperationsTest.cs @@ -506,20 +506,6 @@ public void TestCreateOrUpdateNoErrorBody() } } - /// - /// Test - /// - [Fact] - public void TestCreateOrUpdateWithRetryAfter() - { - var tokenCredentials = new TokenCredentials("123", "abc"); - var handler = new PlaybackTestHandler(LROResponse.MockCreateOrUpdateWithRetryAfterTwoTries()); - var fakeClient = new RedisManagementClient(tokenCredentials, handler); - var now = DateTime.Now; - fakeClient.RedisOperations.CreateOrUpdate("rg", "redis", new RedisCreateOrUpdateParameters(), "1234"); - - Assert.True(DateTime.Now - now >= TimeSpan.FromSeconds(2)); - } /// /// Test @@ -600,20 +586,7 @@ public void TestDeleteWithLocationHeaderErrorHandlingSecondTime() Assert.Equal("Long running operation failed with status 'InternalServerError'.", ex.Message); } - /// - /// Test - /// - [Fact] - public void TestDeleteWithRetryAfter() - { - var tokenCredentials = new TokenCredentials("123", "abc"); - var handler = new PlaybackTestHandler(LROResponse.MockDeleteWithRetryAfterTwoTries()); - var fakeClient = new RedisManagementClient(tokenCredentials, handler); - var now = DateTime.Now; - fakeClient.RedisOperations.Delete("rg", "redis", "1234"); - - Assert.True(DateTime.Now - now >= TimeSpan.FromSeconds(2)); - } + /// @@ -692,4 +665,97 @@ public void TestLROWithNonStandardTerminalStatus() Assert.Equal("OK", foo.Response.StatusCode.ToString()); } } + + /// + /// Tests for Retry-After header + /// + public class LRO_RetryAfterTests + { + + /// + /// Test + /// + [Fact] + public void TestCreateOrUpdateWithRetryAfter() + { + var tokenCredentials = new TokenCredentials("123", "abc"); + var handler = new PlaybackTestHandler(LROResponse.MockCreateOrUpdateWithRetryAfterTwoTries()); + var fakeClient = new RedisManagementClient(tokenCredentials, handler); + var now = DateTime.Now; + fakeClient.RedisOperations.CreateOrUpdate("rg", "redis", new RedisCreateOrUpdateParameters(), "1234"); + + Assert.True(DateTime.Now - now >= TimeSpan.FromSeconds(2)); + } + + /// + /// Test + /// + [Fact] + public void TestDeleteWithRetryAfter() + { + var tokenCredentials = new TokenCredentials("123", "abc"); + var handler = new PlaybackTestHandler(LROResponse.MockDeleteWithRetryAfterTwoTries()); + var fakeClient = new RedisManagementClient(tokenCredentials, handler); + var now = DateTime.Now; + fakeClient.RedisOperations.Delete("rg", "redis", "1234"); + + Assert.True(DateTime.Now - now >= TimeSpan.FromSeconds(2)); + } + + /// + /// Test + /// + [Fact] + public void TestPatchWithRetryAfter() + { + var tokenCredentials = new TokenCredentials("123", "abc"); + var handler = new PlaybackTestHandler(LROResponse.MockPatchWithRetryAfterTwoTries()); + var fakeClient = new RedisManagementClient(tokenCredentials, handler); + var now = DateTime.Now; + fakeClient.RedisOperations.Patch("rg", "redis", new RedisCreateOrUpdateParameters(), "1234"); + Assert.True(DateTime.Now - now >= TimeSpan.FromSeconds(2)); + } + + /// + /// Test + /// + [Fact] + public void TestCreateWithDifferentRetryAfter() + { + var tokenCredentials = new TokenCredentials("123", "abc"); + var handler = new PlaybackTestHandler(LROResponse.MockCreateOrUpdateWithDifferentRetryAfterValues()); + var fakeClient = new RedisManagementClient(tokenCredentials, handler); + var before = DateTime.Now; + fakeClient.RedisOperations.CreateOrUpdate("rg", "redis", new RedisCreateOrUpdateParameters(), "1234"); + Assert.True(DateTime.Now - before >= TimeSpan.FromSeconds(7)); + } + + /// + /// + /// + [Fact] + public void TestCreateWithRetryAfterDefaultMin() + { + var tokenCredentials = new TokenCredentials("123", "abc"); + var handler = new PlaybackTestHandler(LROResponse.MockCreateWithRetryAfterDefaultMin()); + var fakeClient = new RedisManagementClient(tokenCredentials, handler); + var before = DateTime.Now; + fakeClient.RedisOperations.CreateOrUpdate("rg", "redis", new RedisCreateOrUpdateParameters(), "1234"); + Assert.True(DateTime.Now - before >= TimeSpan.FromSeconds(1)); + } + + /// + /// + /// + [Fact] + public void TestCreateWithRetryAfterDefaultMax() + { + var tokenCredentials = new TokenCredentials("123", "abc"); + var handler = new PlaybackTestHandler(LROResponse.MockCreateWithRetryAfterDefaultMax()); + var fakeClient = new RedisManagementClient(tokenCredentials, handler); + var before = DateTime.Now; + fakeClient.RedisOperations.CreateOrUpdate("rg", "redis", new RedisCreateOrUpdateParameters(), "1234"); + Assert.True(DateTime.Now - before >= TimeSpan.FromSeconds(40)); + } + } } diff --git a/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure.Tests/Microsoft.Rest.ClientRuntime.Azure.Tests.csproj b/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure.Tests/Microsoft.Rest.ClientRuntime.Azure.Tests.csproj index f4df8abb7ffb..79ce3ac0e427 100644 --- a/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure.Tests/Microsoft.Rest.ClientRuntime.Azure.Tests.csproj +++ b/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure.Tests/Microsoft.Rest.ClientRuntime.Azure.Tests.csproj @@ -2,14 +2,13 @@ Test Project for Microsoft.Rest.ClientRuntime.Azure - 2.0.0-preview + 1.0.0 Microsoft.Rest.ClientRuntime.Azure.Tests Microsoft.Rest.ClientRuntime.Azure.Tests Rest ClientRuntime Azure Test $(NugetCommonTags) $(NugetCommonProfileTags) netcoreapp1.1 - true 1701;1702;1705 @@ -19,13 +18,7 @@ - - - - diff --git a/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure.Tests/Sample/RedisManagementClient.cs b/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure.Tests/Sample/RedisManagementClient.cs index 17e4b94ad438..6d949a9e6657 100644 --- a/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure.Tests/Sample/RedisManagementClient.cs +++ b/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure.Tests/Sample/RedisManagementClient.cs @@ -760,6 +760,24 @@ public static RedisResource CreateOrUpdate(this IRedisOperations operations, str , operations, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default).Unwrap().GetAwaiter().GetResult(); } + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static RedisResource Patch(this IRedisOperations operations, string resoruceGroupName, string name, RedisCreateOrUpdateParameters parameters, string subscriptionId) + { + return Task.Factory.StartNew((object s) => + { + return ((IRedisOperations)s).PatchAsync(resoruceGroupName, name, parameters, subscriptionId); + } + ,operations, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default).Unwrap().GetAwaiter().GetResult(); + } + /// /// Create a redis cache, or replace (overwrite/recreate, with /// potential downtime) an existing cache @@ -843,6 +861,22 @@ public static RedisSubResource CreateOrUpdateSubResource(this IRedisOperations o AzureOperationResponse result = await operations.CreateOrUpdateWithHttpMessagesAsync(resourceGroupName, name, parameters, subscriptionId, cancellationToken).ConfigureAwait(false); return result.Body; } + + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static async Task PatchAsync(this IRedisOperations operations, string resourceGroupName, string name, RedisCreateOrUpdateParameters parameters, string subscriptionId, CancellationToken cancellationToken = default(System.Threading.CancellationToken)) + { + AzureOperationResponse result = await operations.PatchWithHttpMessagesAsync(resourceGroupName, name, parameters, subscriptionId, cancellationToken).ConfigureAwait(false); + return result.Body; + } /// /// Create a redis cache, or replace (overwrite/recreate, with @@ -1065,6 +1099,9 @@ public partial interface IRedisOperations Task> CreateOrUpdateWithHttpMessagesAsync(string resourceGroupName, string name, RedisCreateOrUpdateParameters parameters, string subscriptionId, CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + Task> PatchWithHttpMessagesAsync(string resourceGroupName, string name, RedisCreateOrUpdateParameters parameters, string subscriptionId, + CancellationToken cancellationToken = default(CancellationToken)); + /// /// Create a redis cache, or replace (overwrite/recreate, with /// potential downtime) an existing cache @@ -1175,25 +1212,19 @@ public RedisManagementClient Client } /// - /// Create a redis cache, or replace (overwrite/recreate, with - /// potential downtime) an existing cache + /// /// - /// - /// Required. - /// - /// - /// Required. - /// - /// - /// Required. - /// - /// - /// Required. - /// - /// - /// Cancellation token. - /// - public async Task> BeginCreateOrUpdateWithHttpMessagesAsync(string resourceGroupName, string name, RedisCreateOrUpdateParameters parameters, string subscriptionId, CancellationToken cancellationToken = default(System.Threading.CancellationToken)) + /// + /// + /// + /// + /// + /// + /// + public async Task> BeginCreateOrUpdateWithHttpMessagesAsync(string resourceGroupName, + string name, RedisCreateOrUpdateParameters parameters, + string subscriptionId, HttpMethod httpMethod, + CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { // Validate if (resourceGroupName == null) @@ -1250,7 +1281,7 @@ public RedisManagementClient Client // Create HTTP transport objects HttpRequestMessage httpRequest = new HttpRequestMessage(); - httpRequest.Method = HttpMethod.Put; + httpRequest.Method = httpMethod; httpRequest.RequestUri = new Uri(url); // Set Headers @@ -1291,7 +1322,7 @@ public RedisManagementClient Client } ex.Request = new HttpRequestMessageWrapper(httpRequest, requestContent); ex.Response = new HttpResponseMessageWrapper(httpResponse, responseContent); - + if (shouldTrace) { ServiceClientTracing.Error(invocationId, ex); @@ -1318,7 +1349,8 @@ public RedisManagementClient Client return result; } - public async Task> CreateOrUpdateWithHttpMessagesAsync(string resourceGroupName, string name, RedisCreateOrUpdateParameters parameters, string subscriptionId, CancellationToken cancellationToken = default(System.Threading.CancellationToken)) + public async Task> PatchWithHttpMessagesAsync(string resourceGroupName, string name, RedisCreateOrUpdateParameters parameters, string subscriptionId, + CancellationToken cancellationToken = default(CancellationToken)) { // Send Request AzureOperationResponse response = await BeginCreateOrUpdateWithHttpMessagesAsync( @@ -1326,17 +1358,68 @@ public RedisManagementClient Client name, parameters, subscriptionId, + new HttpMethod("PATCH"), cancellationToken); - Debug.Assert(response.Response.StatusCode == HttpStatusCode.OK || + Debug.Assert(response.Response.StatusCode == HttpStatusCode.OK || + response.Response.StatusCode == HttpStatusCode.Created || + response.Response.StatusCode == HttpStatusCode.Accepted); + + return await this.Client.GetPutOrPatchOperationResultAsync(response, + null, + cancellationToken); + } + public async Task> CreateOrUpdateWithHttpMessagesAsync(string resourceGroupName, string name, + RedisCreateOrUpdateParameters parameters, string subscriptionId, + CancellationToken cancellationToken = default(System.Threading.CancellationToken)) + { + // Send Request + AzureOperationResponse response = await BeginCreateOrUpdateWithHttpMessagesAsync( + resourceGroupName, + name, + parameters, + subscriptionId, + cancellationToken); + + Debug.Assert(response.Response.StatusCode == HttpStatusCode.OK || response.Response.StatusCode == HttpStatusCode.Created || response.Response.StatusCode == HttpStatusCode.Accepted); - return await this.Client.GetPutOrPatchOperationResultAsync(response, + return await this.Client.GetPutOrPatchOperationResultAsync(response, null, cancellationToken); } + + /// + /// Create a redis cache, or replace (overwrite/recreate, with + /// potential downtime) an existing cache + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// + /// + /// Cancellation token. + /// + public async Task> BeginCreateOrUpdateWithHttpMessagesAsync(string resourceGroupName, + string name, RedisCreateOrUpdateParameters parameters, + string subscriptionId, + CancellationToken cancellationToken = default(System.Threading.CancellationToken)) + { + return await BeginCreateOrUpdateWithHttpMessagesAsync(resourceGroupName, name, parameters, subscriptionId, HttpMethod.Put, cancellationToken); + } + /// /// Create a redis cache, or replace (overwrite/recreate, with /// potential downtime) an existing cache diff --git a/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure/AzureClientExtensions.cs b/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure/AzureClientExtensions.cs index a89cfbf888f9..4154abf8f7b7 100644 --- a/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure/AzureClientExtensions.cs +++ b/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure/AzureClientExtensions.cs @@ -1,21 +1,21 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Rest.ClientRuntime.Azure.Properties; -using Microsoft.Rest.Serialization; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - namespace Microsoft.Rest.Azure { + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using System.Net; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Rest.ClientRuntime.Azure.Properties; + using Microsoft.Rest.Serialization; + using Newtonsoft.Json; + using Newtonsoft.Json.Linq; + public static partial class AzureClientExtensions { /// @@ -106,7 +106,7 @@ public static async Task> GetLongRunningO while (!AzureAsyncOperation.TerminalStatuses.Any(s => s.Equals(pollingState.Status, StringComparison.OrdinalIgnoreCase))) { - await Task.Delay(pollingState.DelayInMilliseconds, cancellationToken).ConfigureAwait(false); + await Task.Delay(pollingState.DelayBetweenPolling, cancellationToken).ConfigureAwait(false); if (!string.IsNullOrEmpty(pollingState.AzureAsyncOperationHeaderLink)) { @@ -349,7 +349,7 @@ private static bool CheckResponseStatusCodeFailed( var statusCode = initialResponse.Response.StatusCode; var method = initialResponse.Request.Method; if (statusCode == HttpStatusCode.OK || statusCode == HttpStatusCode.Accepted || - (statusCode == HttpStatusCode.Created && method == HttpMethod.Put) || + (statusCode == HttpStatusCode.Created && (method == HttpMethod.Put)) || (statusCode == HttpStatusCode.NoContent && (method == HttpMethod.Delete || method == HttpMethod.Post))) { return false; diff --git a/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure/AzureLROOperationExtensions.cs b/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure/AzureLROOperationExtensions.cs index 016e6df15661..57ed22e34136 100644 --- a/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure/AzureLROOperationExtensions.cs +++ b/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure/AzureLROOperationExtensions.cs @@ -17,7 +17,6 @@ namespace Microsoft.Rest.Azure public static partial class AzureClientExtensions { - /// /// Updates PollingState from Location header. /// diff --git a/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure/Microsoft.Rest.ClientRuntime.Azure.csproj b/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure/Microsoft.Rest.ClientRuntime.Azure.csproj index 67836bec78b2..24d98a5afd70 100644 --- a/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure/Microsoft.Rest.ClientRuntime.Azure.csproj +++ b/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure/Microsoft.Rest.ClientRuntime.Azure.csproj @@ -4,7 +4,7 @@ Microsoft.Rest.ClientRuntime.Azure Provides common error handling, tracing, and HTTP/REST-based pipeline manipulation. Client Runtime for Microsoft Azure Libraries - 3.3.9 + 3.4.0 Microsoft.Rest.ClientRuntime.Azure Microsoft Azure AutoRest ClientRuntime REST $(NugetCommonTags) $(NugetCommonProfileTags) diff --git a/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure/PollingState.cs b/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure/PollingState.cs index c516c5043d94..2f7dc1f15b8c 100644 --- a/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure/PollingState.cs +++ b/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure/PollingState.cs @@ -8,6 +8,7 @@ using Microsoft.Rest.ClientRuntime.Azure.Properties; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using System; namespace Microsoft.Rest.Azure { @@ -18,6 +19,18 @@ namespace Microsoft.Rest.Azure /// Type of resource header. internal class PollingState where TBody : class where THeader : class { +#if DEBUG + const int DEFAULT_MIN_DELAY_SECONDS = 1; + const int DEFAULT_MAX_DELAY_SECONDS = 40; +#else + // Delay values are in seconds + const int DEFAULT_MIN_DELAY_SECONDS = 10; + const int DEFAULT_MAX_DELAY_SECONDS = 600; +#endif + + private int _retryAfterInSeconds; + + /// /// Initializes an instance of PollingState. /// @@ -25,7 +38,7 @@ internal class PollingState where TBody : class where THeader : /// Default timeout. public PollingState(HttpOperationResponse response, int? retryTimeout) { - _retryTimeout = retryTimeout; + RetryAfterInSeconds = retryTimeout.HasValue ? retryTimeout.Value : AzureAsyncOperation.DefaultDelay; Response = response.Response; Request = response.Request; Resource = response.Body; @@ -136,6 +149,11 @@ public HttpResponseMessage Response { LocationHeaderLink = _response.Headers.GetValues("Location").FirstOrDefault(); } + + if (_response.Headers.Contains("Retry-After")) + { + RetryAfterInSeconds = int.Parse(_response.Headers.GetValues("Retry-After").FirstOrDefault(), CultureInfo.InvariantCulture); + } } } } @@ -160,28 +178,78 @@ public HttpResponseMessage Response /// public THeader ResourceHeaders { get; set; } - private int? _retryTimeout; + //private int? _retryTimeout; /// /// Gets long running operation delay in milliseconds. /// + [Obsolete("DelayInMilliseconds property will be deprecated in future releases. You should start using DelayBetweenPolling")] public int DelayInMilliseconds { get { - if (_retryTimeout != null) - { - return _retryTimeout.Value * 1000; - } - if (Response != null && Response.Headers.Contains("Retry-After")) - { - return int.Parse(Response.Headers.GetValues("Retry-After").FirstOrDefault(), - CultureInfo.InvariantCulture) * 1000; - } - return AzureAsyncOperation.DefaultDelay * 1000; + return RetryAfterInSeconds * 1000; + } + } + + /// + /// Long running operation polling delay + /// + public TimeSpan DelayBetweenPolling + { + get + { + return TimeSpan.FromSeconds(RetryAfterInSeconds); } } + /// + /// Initially this is initialized with LongRunningOperationRetryTimeout value + /// Verify min/max allowed value according to ARM spec (especially minimum value for throttling at ARM level) + /// We want this to be int value and not int? because this value will always have a default non-zero/non-null value + /// + internal int RetryAfterInSeconds + { + get + { + return _retryAfterInSeconds; + } + + set + { + _retryAfterInSeconds = ValidateRetryAfterValue(value); + } + } + + + private int ValidateRetryAfterValue(int? currentValue) + { + if (currentValue.HasValue) + { + if (currentValue < DEFAULT_MIN_DELAY_SECONDS) + currentValue = DEFAULT_MIN_DELAY_SECONDS; + else if (currentValue > DEFAULT_MAX_DELAY_SECONDS) + currentValue = DEFAULT_MAX_DELAY_SECONDS; + } + else + { + currentValue = AzureAsyncOperation.DefaultDelay; + } + + return currentValue.Value; + } + + //internal int GetRetryAfterValueFromHeader(HttpResponseMessage responseMessage) + //{ + // int retryAfter = 0; + // if (responseMessage != null && responseMessage.Headers.Contains("Retry-After")) + // { + // retryAfter = int.Parse(responseMessage.Headers.GetValues("Retry-After").FirstOrDefault(), CultureInfo.InvariantCulture); + // } + + // return retryAfter; + //} + /// /// Gets CloudException from current instance. /// diff --git a/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure/Properties/AssemblyInfo.cs b/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure/Properties/AssemblyInfo.cs index a0549f7eba43..960c18d502b3 100644 --- a/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure/Properties/AssemblyInfo.cs +++ b/src/SdkCommon/ClientRuntime.Azure/ClientRuntime.Azure/Properties/AssemblyInfo.cs @@ -9,7 +9,7 @@ [assembly: AssemblyTitle("Microsoft Rest Azure Client Runtime")] [assembly: AssemblyDescription("Client infrastructure for Azure client libraries.")] [assembly: AssemblyVersion("3.0.0.0")] -[assembly: AssemblyFileVersion("3.3.9.0")] +[assembly: AssemblyFileVersion("3.4.0.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Microsoft Corporation")]