Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public class AzureAppConfigurationOptions
{
private const int MaxRetries = 2;
private static readonly TimeSpan MaxRetryDelay = TimeSpan.FromMinutes(1);
private static readonly TimeSpan NetworkTimeout = TimeSpan.FromSeconds(10);
private static readonly KeyValueSelector DefaultQuery = new KeyValueSelector { KeyFilter = KeyFilter.Any, LabelFilter = LabelFilter.Null };

private List<KeyValueWatcher> _individualKvWatchers = new List<KeyValueWatcher>();
Expand Down Expand Up @@ -529,6 +530,7 @@ private static ConfigurationClientOptions GetDefaultClientOptions()
clientOptions.Retry.MaxRetries = MaxRetries;
clientOptions.Retry.MaxDelay = MaxRetryDelay;
clientOptions.Retry.Mode = RetryMode.Exponential;
clientOptions.Retry.NetworkTimeout = NetworkTimeout;
clientOptions.AddPolicy(new UserAgentHeaderPolicy(), HttpPipelinePosition.PerCall);

return clientOptions;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1232,6 +1232,11 @@ await ExecuteWithFailOverPolicyAsync<object>(clients, async (client) =>

private bool IsFailOverable(AggregateException ex)
{
if (ex.InnerExceptions?.Any(e => e is TaskCanceledException) == true)
{
return true;
}

RequestFailedException rfe = ex.InnerExceptions?.LastOrDefault(e => e is RequestFailedException) as RequestFailedException;

return rfe != null ? IsFailOverable(rfe) : false;
Expand Down
79 changes: 79 additions & 0 deletions tests/Tests.AzureAppConfiguration/FailoverTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -337,5 +337,84 @@ public void FailOverTests_GetNoDynamicClient()
// Only contains the client that passed while constructing the ConfigurationClientManager
Assert.Single(clients);
}

[Fact]
public void FailOverTests_NetworkTimeout()
{
// Arrange
IConfigurationRefresher refresher = null;
var mockResponse = new Mock<Response>();

var client1 = new ConfigurationClient(TestHelpers.CreateMockEndpointString(),
new ConfigurationClientOptions()
{
Retry =
{
NetworkTimeout = TimeSpan.FromTicks(1)
}
});

var mockClient2 = new Mock<ConfigurationClient>();
mockClient2.Setup(c => c.GetConfigurationSettingsAsync(It.IsAny<SettingSelector>(), It.IsAny<CancellationToken>()))
.Returns(new MockAsyncPageable(Enumerable.Empty<ConfigurationSetting>().ToList()));
mockClient2.Setup(c => c.GetConfigurationSettingAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(Response.FromValue<ConfigurationSetting>(kv, mockResponse.Object)));
mockClient2.Setup(c => c.GetConfigurationSettingAsync(It.IsAny<ConfigurationSetting>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(Response.FromValue<ConfigurationSetting>(kv, mockResponse.Object)));
mockClient2.Setup(c => c.Equals(mockClient2)).Returns(true);

ConfigurationClientWrapper cw1 = new ConfigurationClientWrapper(TestHelpers.PrimaryConfigStoreEndpoint, client1);
ConfigurationClientWrapper cw2 = new ConfigurationClientWrapper(TestHelpers.SecondaryConfigStoreEndpoint, mockClient2.Object);

var clientList = new List<ConfigurationClientWrapper>() { cw1 };
var autoFailoverList = new List<ConfigurationClientWrapper>() { cw2 };
var configClientManager = new MockedConfigurationClientManager(clientList, autoFailoverList);

// Make sure the provider fails over and will load correctly using the second client
var config = new ConfigurationBuilder()
.AddAzureAppConfiguration(options =>
{
options.ClientManager = configClientManager;
options.Select("TestKey*");
options.ConfigureRefresh(refreshOptions =>
{
refreshOptions.Register("TestKey1", "label")
.SetRefreshInterval(TimeSpan.FromSeconds(1));
});
})
.Build();

// Make sure the provider fails on startup and throws the expected exception due to startup timeout
Exception exception = Assert.Throws<TimeoutException>(() =>
{
config = new ConfigurationBuilder()
.AddAzureAppConfiguration(options =>
{
options.Connect(TestHelpers.CreateMockEndpointString());
options.Select("TestKey*");
options.ConfigureRefresh(refreshOptions =>
{
refreshOptions.Register("TestKey1", "label")
.SetRefreshInterval(TimeSpan.FromSeconds(1));
});
options.ConfigureStartupOptions(startup =>
{
startup.Timeout = TimeSpan.FromSeconds(5);
});
options.ConfigureClientOptions(clientOptions =>
{
clientOptions.Retry.NetworkTimeout = TimeSpan.FromTicks(1);
});
})
.Build();
});

// Make sure the startup exception is due to network timeout
// Aggregate exception is nested due to how provider stores all startup exceptions thrown
Assert.True(exception.InnerException is AggregateException ae &&
ae.InnerException is AggregateException ae2 &&
ae2.InnerExceptions.All(ex => ex is TaskCanceledException) &&
ae2.InnerException is TaskCanceledException tce);
}
}
}