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 @@ -630,6 +630,17 @@ private async Task<bool> TryInitializeAsync(IEnumerable<ConfigurationClient> cli

throw;
}
catch (KeyVaultReferenceException exception)
{
if (IsFailOverable(exception))
{
startupExceptions.Add(exception);

return false;
}

throw;
}
catch (AggregateException exception)
{
if (exception.InnerExceptions?.Any(e => e is OperationCanceledException) ?? false)
Expand Down Expand Up @@ -980,6 +991,15 @@ private async Task<T> ExecuteWithFailOverPolicyAsync<T>(
throw;
}
}
catch (KeyVaultReferenceException kvre)
{
if (!IsFailOverable(kvre) || !clientEnumerator.MoveNext())
{
backoffAllClients = true;

throw;
}
}
catch (AggregateException ae)
{
if (!IsFailOverable(ae) || !clientEnumerator.MoveNext())
Expand Down Expand Up @@ -1066,6 +1086,20 @@ innerException is SocketException ||
innerException is IOException;
}

private bool IsFailOverable(KeyVaultReferenceException kvre)
{
if (kvre.InnerException is RequestFailedException rfe && IsFailOverable(rfe))
{
return true;
}
else if (kvre.InnerException is AggregateException ae && IsFailOverable(ae))
{
return true;
}

return false;
}

private async Task<Dictionary<string, ConfigurationSetting>> MapConfigurationSettings(Dictionary<string, ConfigurationSetting> data)
{
Dictionary<string, ConfigurationSetting> mappedData = new Dictionary<string, ConfigurationSetting>(StringComparer.OrdinalIgnoreCase);
Expand Down
53 changes: 53 additions & 0 deletions tests/Tests.AzureAppConfiguration/FailoverTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -257,5 +257,58 @@ public void FailOverTests_BackoffStateIsUpdatedOnSuccessfulRequest()
// The client enumerator should return 2 clients for the third time.
Assert.Equal(2, configClientManager.GetAvailableClients(DateTimeOffset.UtcNow).Count());
}

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

var mockClient1 = new Mock<ConfigurationClient>();
mockClient1.Setup(c => c.GetConfigurationSettingsAsync(It.IsAny<SettingSelector>(), It.IsAny<CancellationToken>()))
.Throws(new KeyVaultReferenceException("Key vault reference failed.", new RequestFailedException(503, "Request failed.")));
mockClient1.Setup(c => c.GetConfigurationSettingAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
.Throws(new KeyVaultReferenceException("Key vault reference failed.", new RequestFailedException(503, "Request failed.")));
mockClient1.Setup(c => c.GetConfigurationSettingAsync(It.IsAny<ConfigurationSetting>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.Throws(new KeyVaultReferenceException("Key vault reference failed.", new RequestFailedException(503, "Request failed.")));
mockClient1.Setup(c => c.Equals(mockClient1)).Returns(true);

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, mockClient1.Object);
ConfigurationClientWrapper cw2 = new ConfigurationClientWrapper(TestHelpers.SecondaryConfigStoreEndpoint, mockClient2.Object);

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

var config = new ConfigurationBuilder()
.AddAzureAppConfiguration(options =>
{
options.ClientManager = configClientManager;
options.Select("TestKey*");
options.ConfigureRefresh(refreshOptions =>
{
refreshOptions.Register("TestKey1", "label")
.SetCacheExpiration(TimeSpan.FromSeconds(1));
});

refresher = options.GetRefresher();
})
.Build();

// The client enumerator should return just 1 client.
Assert.Single(configClientManager.GetAvailableClients(DateTimeOffset.UtcNow));

// The build should be successful since one client was backed off and it failed over to the second client.
Assert.Equal("TestValue1", config["TestKey1"]);
}
}
}