Skip to content
Closed
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 @@ -37,17 +37,27 @@ public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context
return HealthCheckResult.Unhealthy(HealthCheckConstants.NoProviderFoundMessage);
}

HealthCheckResult worstResult = HealthCheckResult.Healthy();

foreach (IHealthCheck healthCheck in _healthChecks)
{
var result = await healthCheck.CheckHealthAsync(context, cancellationToken).ConfigureAwait(false);

// Keep track of the worst health status found
// HealthStatus enum is ordered: Unhealthy(0) < Degraded(1) < Healthy(2)
if (result.Status < worstResult.Status)
{
worstResult = result;
}

// If an Unhealthy status is found, short-circuit since that's the worst possible
if (result.Status == HealthStatus.Unhealthy)
{
return result;
}
}

return HealthCheckResult.Healthy();
return worstResult;
}

private void FindHealthChecks(IConfigurationRoot configurationRoot, List<IHealthCheck> healthChecks)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -576,15 +576,17 @@ public void ProcessPushNotification(PushNotification pushNotification, TimeSpan?

public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
HealthStatus failureStatus = context.Registration?.FailureStatus ?? HealthStatus.Unhealthy;

if (!_lastSuccessfulAttempt.HasValue)
{
return HealthCheckResult.Unhealthy(HealthCheckConstants.LoadNotCompletedMessage);
return new HealthCheckResult(status: failureStatus, description: HealthCheckConstants.LoadNotCompletedMessage);
}

if (_lastFailedAttempt.HasValue &&
_lastSuccessfulAttempt.Value < _lastFailedAttempt.Value)
{
return HealthCheckResult.Unhealthy(HealthCheckConstants.RefreshFailedMessage);
return new HealthCheckResult(status: failureStatus, description: HealthCheckConstants.RefreshFailedMessage);
}

return HealthCheckResult.Healthy();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,14 @@ private void FindRefreshers(IConfigurationRoot configurationRoot, ILoggerFactory
{
foreach (IConfigurationProvider provider in configurationRoot.Providers)
{
if (provider is AzureAppConfigurationProvider appConfigurationProvider)
if (provider is IConfigurationRefresher configurationRefresher)
{
appConfigurationProvider.LoggerFactory = loggerFactory;
refreshers.Add(appConfigurationProvider);
refreshers.Add(configurationRefresher);

if (configurationRefresher is AzureAppConfigurationProvider azureAppConfigurationProvider)
{
azureAppConfigurationProvider.LoggerFactory = loggerFactory;
}
}
else if (provider is ChainedConfigurationProvider chainedProvider)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public static async Task<bool> HaveCollectionsChanged(
LabelFilter = keyValueSelector.LabelFilter
};

AsyncPageable<ConfigurationSetting> pageable = client.GetConfigurationSettingsAsync(selector, cancellationToken);
AsyncPageable<ConfigurationSetting> pageable = client.CheckConfigurationSettingsAsync(selector, cancellationToken);

using IEnumerator<WatchedPage> existingPageWatcherEnumerator = pageWatchers.GetEnumerator();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<Import Project="..\..\build\NugetProperties.props" />

Expand All @@ -15,7 +15,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Azure.Data.AppConfiguration" Version="1.7.0" />
<PackageReference Include="Azure.Data.AppConfiguration" Version="1.8.0" />
<PackageReference Include="Azure.Messaging.EventGrid" Version="5.0.0" />
<PackageReference Include="Azure.Security.KeyVault.Secrets" Version="4.8.0" />
<PackageReference Include="DnsClient" Version="1.7.0" />
Expand Down
18 changes: 11 additions & 7 deletions tests/Tests.AzureAppConfiguration/Unit/AfdTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -242,10 +242,12 @@ public async Task AfdTests_RegisterAllRefresh()
var mockAsyncPageable4 = new MockAsyncPageable(keyValueCollection3, null, 3, responses4);

mockClient.SetupSequence(c => c.GetConfigurationSettingsAsync(It.IsAny<SettingSelector>(), It.IsAny<CancellationToken>()))
.Returns(mockAsyncPageable1)
.Returns(mockAsyncPageable2)
.Returns(mockAsyncPageable3)
.Returns(mockAsyncPageable4);
.Returns(mockAsyncPageable1) // initial load
.Returns(mockAsyncPageable3); // reload after change detected

mockClient.SetupSequence(c => c.CheckConfigurationSettingsAsync(It.IsAny<SettingSelector>(), It.IsAny<CancellationToken>()))
.Returns(mockAsyncPageable2) // first check - stale, should not refresh
.Returns(mockAsyncPageable3); // second check - should trigger refresh

var afdEndpoint = new Uri("https://test.b01.azurefd.net");
IConfigurationRefresher refresher = null;
Expand Down Expand Up @@ -381,10 +383,12 @@ public async Task AfdTests_FeatureFlagsRefresh()
mockClient.SetupSequence(c => c.GetConfigurationSettingsAsync(It.IsAny<SettingSelector>(), It.IsAny<CancellationToken>()))
.Returns(mockAsyncPageable1) // default load configuration settings
.Returns(mockAsyncPageable1) // load feature flag
.Returns(mockAsyncPageable3) // reload after change detected
.Returns(mockAsyncPageable3); // reload feature flags

mockClient.SetupSequence(c => c.CheckConfigurationSettingsAsync(It.IsAny<SettingSelector>(), It.IsAny<CancellationToken>()))
.Returns(mockAsyncPageable2) // watch request, should not trigger refresh
.Returns(mockAsyncPageable3) // watch request, should trigger refresh
.Returns(mockAsyncPageable3) // default load configuration settings
.Returns(mockAsyncPageable3); // load feature flag
.Returns(mockAsyncPageable3); // watch request, should trigger refresh

var afdEndpoint = new Uri("https://test.b01.azurefd.net");
IConfigurationRefresher refresher = null;
Expand Down
46 changes: 45 additions & 1 deletion tests/Tests.AzureAppConfiguration/Unit/FeatureManagementTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -777,6 +777,10 @@ public async Task WatchesFeatureFlags()
.Callback(() => mockAsyncPageable.UpdateCollection(featureFlags))
.Returns(mockAsyncPageable);

mockClient.Setup(c => c.CheckConfigurationSettingsAsync(It.IsAny<SettingSelector>(), It.IsAny<CancellationToken>()))
.Callback(() => mockAsyncPageable.UpdateCollection(featureFlags))
.Returns(mockAsyncPageable);

IConfigurationRefresher refresher = null;
var config = new ConfigurationBuilder()
.AddAzureAppConfiguration(options =>
Expand Down Expand Up @@ -849,6 +853,10 @@ public async Task WatchesFeatureFlagsUsingCacheExpirationInterval()
.Callback(() => mockAsyncPageable.UpdateCollection(featureFlags))
.Returns(mockAsyncPageable);

mockClient.Setup(c => c.CheckConfigurationSettingsAsync(It.IsAny<SettingSelector>(), It.IsAny<CancellationToken>()))
.Callback(() => mockAsyncPageable.UpdateCollection(featureFlags))
.Returns(mockAsyncPageable);

var cacheExpirationInterval = TimeSpan.FromSeconds(1);

IConfigurationRefresher refresher = null;
Expand Down Expand Up @@ -923,6 +931,10 @@ public async Task SkipRefreshIfRefreshIntervalHasNotElapsed()
.Callback(() => mockAsyncPageable.UpdateCollection(featureFlags))
.Returns(mockAsyncPageable);

mockClient.Setup(c => c.CheckConfigurationSettingsAsync(It.IsAny<SettingSelector>(), It.IsAny<CancellationToken>()))
.Callback(() => mockAsyncPageable.UpdateCollection(featureFlags))
.Returns(mockAsyncPageable);

IConfigurationRefresher refresher = null;
var config = new ConfigurationBuilder()
.AddAzureAppConfiguration(options =>
Expand Down Expand Up @@ -994,6 +1006,10 @@ public async Task SkipRefreshIfCacheNotExpired()
.Callback(() => mockAsyncPageable.UpdateCollection(featureFlags))
.Returns(mockAsyncPageable);

mockClient.Setup(c => c.CheckConfigurationSettingsAsync(It.IsAny<SettingSelector>(), It.IsAny<CancellationToken>()))
.Callback(() => mockAsyncPageable.UpdateCollection(featureFlags))
.Returns(mockAsyncPageable);

IConfigurationRefresher refresher = null;
var config = new ConfigurationBuilder()
.AddAzureAppConfiguration(options =>
Expand Down Expand Up @@ -1118,6 +1134,10 @@ public async Task DoesNotUseEtagForFeatureFlagRefresh()
.Callback(() => mockAsyncPageable.UpdateCollection(new List<ConfigurationSetting> { _kv }))
.Returns(mockAsyncPageable);

mockClient.Setup(c => c.CheckConfigurationSettingsAsync(It.IsAny<SettingSelector>(), It.IsAny<CancellationToken>()))
.Callback(() => mockAsyncPageable.UpdateCollection(new List<ConfigurationSetting> { _kv }))
.Returns(mockAsyncPageable);

IConfigurationRefresher refresher = null;
var config = new ConfigurationBuilder()
.AddAzureAppConfiguration(options =>
Expand All @@ -1134,7 +1154,8 @@ public async Task DoesNotUseEtagForFeatureFlagRefresh()
Thread.Sleep(RefreshInterval);

await refresher.TryRefreshAsync();
mockClient.Verify(c => c.GetConfigurationSettingsAsync(It.IsAny<SettingSelector>(), It.IsAny<CancellationToken>()), Times.Exactly(3));
mockClient.Verify(c => c.GetConfigurationSettingsAsync(It.IsAny<SettingSelector>(), It.IsAny<CancellationToken>()), Times.Exactly(2));
mockClient.Verify(c => c.CheckConfigurationSettingsAsync(It.IsAny<SettingSelector>(), It.IsAny<CancellationToken>()), Times.Once());
}

[Fact]
Expand Down Expand Up @@ -1569,6 +1590,12 @@ public async Task DifferentCacheExpirationsForMultipleFeatureFlagRegistrations()
(s.Key.StartsWith(FeatureManagementConstants.FeatureFlagMarker + prefix2) && s.Label == label2 && s.Key != FeatureManagementConstants.FeatureFlagMarker + "App2_Feature3")).ToList()))
.Returns(mockAsyncPageable);

mockClient.Setup(c => c.CheckConfigurationSettingsAsync(It.IsAny<SettingSelector>(), It.IsAny<CancellationToken>()))
.Callback(() => mockAsyncPageable.UpdateCollection(featureFlagCollection.Where(s =>
(s.Key.StartsWith(FeatureManagementConstants.FeatureFlagMarker + prefix1) && s.Label == label1) ||
(s.Key.StartsWith(FeatureManagementConstants.FeatureFlagMarker + prefix2) && s.Label == label2 && s.Key != FeatureManagementConstants.FeatureFlagMarker + "App2_Feature3")).ToList()))
.Returns(mockAsyncPageable);

var config = new ConfigurationBuilder()
.AddAzureAppConfiguration(options =>
{
Expand Down Expand Up @@ -1739,6 +1766,11 @@ public async Task SelectAndRefreshSingleFeatureFlag()
s.Key.Equals(FeatureManagementConstants.FeatureFlagMarker + prefix1) && s.Label == label1).ToList()))
.Returns(mockAsyncPageable);

mockClient.Setup(c => c.CheckConfigurationSettingsAsync(It.IsAny<SettingSelector>(), It.IsAny<CancellationToken>()))
.Callback(() => mockAsyncPageable.UpdateCollection(featureFlagCollection.Where(s =>
s.Key.Equals(FeatureManagementConstants.FeatureFlagMarker + prefix1) && s.Label == label1).ToList()))
.Returns(mockAsyncPageable);

var config = new ConfigurationBuilder()
.AddAzureAppConfiguration(options =>
{
Expand Down Expand Up @@ -1802,6 +1834,10 @@ public async Task ValidateCorrectFeatureFlagLoggedIfModifiedOrRemovedDuringRefre
.Callback(() => mockAsyncPageable.UpdateCollection(featureFlags))
.Returns(mockAsyncPageable);

mockClient.Setup(c => c.CheckConfigurationSettingsAsync(It.IsAny<SettingSelector>(), It.IsAny<CancellationToken>()))
.Callback(() => mockAsyncPageable.UpdateCollection(featureFlags))
.Returns(mockAsyncPageable);

mockClient.Setup(c => c.GetConfigurationSettingAsync(It.IsAny<ConfigurationSetting>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((Func<ConfigurationSetting, bool, CancellationToken, Response<ConfigurationSetting>>)GetIfChanged);

Expand Down Expand Up @@ -1886,6 +1922,10 @@ public async Task ValidateFeatureFlagsUnchangedLogged()
.Callback(() => mockAsyncPageable.UpdateCollection(featureFlags))
.Returns(mockAsyncPageable);

mockClient.Setup(c => c.CheckConfigurationSettingsAsync(It.IsAny<SettingSelector>(), It.IsAny<CancellationToken>()))
.Callback(() => mockAsyncPageable.UpdateCollection(featureFlags))
.Returns(mockAsyncPageable);

mockClient.Setup(c => c.GetConfigurationSettingAsync(It.IsAny<ConfigurationSetting>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((Func<ConfigurationSetting, bool, CancellationToken, Response<ConfigurationSetting>>)GetIfChanged);

Expand Down Expand Up @@ -1964,6 +2004,10 @@ public async Task MapTransformFeatureFlagWithRefresh()
.Callback(() => mockAsyncPageable.UpdateCollection(featureFlags))
.Returns(mockAsyncPageable);

mockClient.Setup(c => c.CheckConfigurationSettingsAsync(It.IsAny<SettingSelector>(), It.IsAny<CancellationToken>()))
.Callback(() => mockAsyncPageable.UpdateCollection(featureFlags))
.Returns(mockAsyncPageable);

mockClient.Setup(c => c.GetConfigurationSettingAsync(It.IsAny<ConfigurationSetting>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((Func<ConfigurationSetting, bool, CancellationToken, Response<ConfigurationSetting>>)GetIfChanged);

Expand Down
55 changes: 55 additions & 0 deletions tests/Tests.AzureAppConfiguration/Unit/HealthCheckTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ public async Task HealthCheckTests_ReturnsUnhealthyWhenRefreshFailed()

mockClient.SetupSequence(c => c.GetConfigurationSettingsAsync(It.IsAny<SettingSelector>(), It.IsAny<CancellationToken>()))
.Returns(new MockAsyncPageable(kvCollection))
.Returns(new MockAsyncPageable(Enumerable.Empty<ConfigurationSetting>().ToList()))
.Returns(new MockAsyncPageable(Enumerable.Empty<ConfigurationSetting>().ToList()));

mockClient.SetupSequence(c => c.CheckConfigurationSettingsAsync(It.IsAny<SettingSelector>(), It.IsAny<CancellationToken>()))
.Throws(new RequestFailedException(503, "Request failed."))
.Returns(new MockAsyncPageable(Enumerable.Empty<ConfigurationSetting>().ToList()))
.Returns(new MockAsyncPageable(Enumerable.Empty<ConfigurationSetting>().ToList()));
Expand Down Expand Up @@ -136,5 +140,56 @@ public async Task HealthCheckTests_RegisterAzureAppConfigurationHealthCheck()
Assert.Contains(HealthCheckConstants.HealthCheckRegistrationName, result.Entries.Keys);
Assert.Equal(HealthStatus.Healthy, result.Entries[HealthCheckConstants.HealthCheckRegistrationName].Status);
}

[Fact]
public async Task HealthCheckTests_ShouldRespectHealthCheckRegistration()
{
IConfigurationRefresher refresher = null;
var mockResponse = new Mock<Response>();
var mockClient = new Mock<ConfigurationClient>(MockBehavior.Strict);

mockClient.SetupSequence(c => c.GetConfigurationSettingsAsync(It.IsAny<SettingSelector>(), It.IsAny<CancellationToken>()))
.Returns(new MockAsyncPageable(kvCollection));

mockClient.SetupSequence(c => c.CheckConfigurationSettingsAsync(It.IsAny<SettingSelector>(), It.IsAny<CancellationToken>()))
.Throws(new RequestFailedException(503, "Request failed."));

var config = new ConfigurationBuilder()
.AddAzureAppConfiguration(options =>
{
options.ClientManager = TestHelpers.CreateMockedConfigurationClientManager(mockClient.Object);
options.MinBackoffDuration = TimeSpan.FromSeconds(2);
options.ConfigurationSettingPageIterator = new MockConfigurationSettingPageIterator();
options.ConfigureRefresh(refreshOptions =>
{
refreshOptions.RegisterAll()
.SetRefreshInterval(TimeSpan.FromSeconds(1));
});
refresher = options.GetRefresher();
})
.Build();

var services = new ServiceCollection();
services.AddSingleton<IConfiguration>(config);
services.AddLogging(); // add logging for health check service
services.AddHealthChecks()
.AddAzureAppConfiguration(
name: "TestName",
failureStatus: HealthStatus.Degraded);
var provider = services.BuildServiceProvider();
var healthCheckService = provider.GetRequiredService<HealthCheckService>();

var result = await healthCheckService.CheckHealthAsync();
Assert.Equal(HealthStatus.Healthy, result.Status);

// Wait for the refresh interval to expire
Thread.Sleep(2000);

await refresher.TryRefreshAsync();
result = await healthCheckService.CheckHealthAsync();
Assert.Equal(HealthStatus.Degraded, result.Status);
Assert.Contains("TestName", result.Entries.Keys);
Assert.Equal(HealthStatus.Degraded, result.Entries["TestName"].Status);
}
}
}
Loading
Loading