-
Notifications
You must be signed in to change notification settings - Fork 547
Add /health/ready application readiness check #781
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
7de0719
Add design spec and implementation plan for readiness health check
KrzysztofPajak da4747f
Add StartupHealthCheck for application readiness
KrzysztofPajak 936966c
Tag health checks as live/ready and register StartupHealthCheck
KrzysztofPajak 426f5f7
Map /health/ready alongside /health/live
KrzysztofPajak 3e394db
Address code review findings for readiness health check
KrzysztofPajak aeb6367
Drop docs/superpowers process artifacts, keep rationale in code
KrzysztofPajak File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
89 changes: 89 additions & 0 deletions
89
src/Tests/Grand.Web.Common.Tests/Infrastructure/HealthChecks/StartupHealthCheckTests.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| using System.Reflection; | ||
| using Grand.Data; | ||
| using Grand.Web.Common.Infrastructure.HealthChecks; | ||
| using Microsoft.Extensions.Diagnostics.HealthChecks; | ||
| using Microsoft.Extensions.Hosting; | ||
| using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
| using Moq; | ||
|
|
||
| namespace Grand.Web.Common.Tests.Infrastructure.HealthChecks; | ||
|
|
||
| [TestClass] | ||
| [DoNotParallelize] | ||
| public class StartupHealthCheckTests | ||
| { | ||
| // DataSettingsManager caches DatabaseIsInstalled() on first call and its public ResetCache() | ||
| // can only force the cached value to false, never back to true - see DataSettingsManager.cs. | ||
| // Resetting the private static instance per test keeps "installed" and "not installed" cases | ||
| // independent instead of depending on test execution order. [DoNotParallelize] on this class | ||
| // stops these tests running concurrently with each other against that same process-wide state. | ||
| private static readonly FieldInfo InstanceField = typeof(DataSettingsManager) | ||
| .GetField("_instance", BindingFlags.NonPublic | BindingFlags.Static) | ||
| ?? throw new InvalidOperationException( | ||
| "DataSettingsManager._instance field not found - has the type changed?"); | ||
|
|
||
| private string _settingsPath = null!; | ||
|
|
||
| [TestInitialize] | ||
| public void Setup() | ||
| { | ||
| InstanceField.SetValue(null, null); | ||
| _settingsPath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.txt"); | ||
| DataSettingsManager.Initialize(_settingsPath); | ||
| } | ||
|
|
||
| [TestCleanup] | ||
| public void Cleanup() | ||
| { | ||
| InstanceField.SetValue(null, null); | ||
| if (File.Exists(_settingsPath)) | ||
| File.Delete(_settingsPath); | ||
| } | ||
|
|
||
| private static Mock<IHostApplicationLifetime> MockLifetime(bool started) | ||
| { | ||
| var cts = new CancellationTokenSource(); | ||
|
KrzysztofPajak marked this conversation as resolved.
Dismissed
|
||
| if (started) cts.Cancel(); | ||
|
|
||
| var lifetime = new Mock<IHostApplicationLifetime>(); | ||
| lifetime.Setup(l => l.ApplicationStarted).Returns(cts.Token); | ||
| return lifetime; | ||
| } | ||
|
|
||
| [TestMethod] | ||
| public async Task CheckHealthAsync_ApplicationNotStarted_ReturnsUnhealthy() | ||
| { | ||
| DataSettingsManager.Instance.LoadDataSettings( | ||
| new DataSettings { ConnectionString = "mongodb://localhost/test", DbProvider = DbProvider.MongoDB }); | ||
|
|
||
| var check = new StartupHealthCheck(MockLifetime(started: false).Object); | ||
|
|
||
| var result = await check.CheckHealthAsync(new HealthCheckContext()); | ||
|
|
||
| Assert.AreEqual(HealthStatus.Unhealthy, result.Status); | ||
| } | ||
|
|
||
| [TestMethod] | ||
| public async Task CheckHealthAsync_StartedButDatabaseNotConfigured_ReturnsUnhealthy() | ||
| { | ||
| // no connection string loaded - DatabaseIsInstalled() evaluates to false on first call | ||
| var check = new StartupHealthCheck(MockLifetime(started: true).Object); | ||
|
|
||
| var result = await check.CheckHealthAsync(new HealthCheckContext()); | ||
|
|
||
| Assert.AreEqual(HealthStatus.Unhealthy, result.Status); | ||
| } | ||
|
|
||
| [TestMethod] | ||
| public async Task CheckHealthAsync_StartedAndDatabaseConfigured_ReturnsHealthy() | ||
| { | ||
| DataSettingsManager.Instance.LoadDataSettings( | ||
| new DataSettings { ConnectionString = "mongodb://localhost/test", DbProvider = DbProvider.MongoDB }); | ||
|
|
||
| var check = new StartupHealthCheck(MockLifetime(started: true).Object); | ||
|
|
||
| var result = await check.CheckHealthAsync(new HealthCheckContext()); | ||
|
|
||
| Assert.AreEqual(HealthStatus.Healthy, result.Status); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
37 changes: 37 additions & 0 deletions
37
src/Web/Grand.Web.Common/Infrastructure/HealthChecks/StartupHealthCheck.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| using Grand.Data; | ||
| using Microsoft.Extensions.Diagnostics.HealthChecks; | ||
| using Microsoft.Extensions.Hosting; | ||
|
|
||
| namespace Grand.Web.Common.Infrastructure.HealthChecks; | ||
|
|
||
| /// <summary> | ||
| /// Reports whether the application has finished starting and is configured with a database | ||
| /// connection. Intentionally does not probe MongoDB or Redis - readiness here covers only the | ||
| /// application process itself. Dependency probing (DB/Redis ping) is a deliberate future | ||
| /// extension, not an oversight. | ||
| /// Note: DataSettingsManager.DatabaseIsInstalled() caches its result after the first call and | ||
| /// can only be forced to false afterward, never back to true, without a process restart - so a | ||
| /// freshly-installed instance keeps returning Unhealthy here until the app restarts post-install | ||
| /// (expected: the installer already asks for a restart once setup completes). | ||
| /// </summary> | ||
| public class StartupHealthCheck : IHealthCheck | ||
| { | ||
| private readonly IHostApplicationLifetime _applicationLifetime; | ||
|
|
||
| public StartupHealthCheck(IHostApplicationLifetime applicationLifetime) | ||
| { | ||
| ArgumentNullException.ThrowIfNull(applicationLifetime); | ||
| _applicationLifetime = applicationLifetime; | ||
| } | ||
|
|
||
| public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, | ||
| CancellationToken cancellationToken = default) | ||
| { | ||
| if (!_applicationLifetime.ApplicationStarted.IsCancellationRequested) | ||
| return Task.FromResult(HealthCheckResult.Unhealthy("Application has not finished starting.")); | ||
|
|
||
| return Task.FromResult(DataSettingsManager.DatabaseIsInstalled() | ||
| ? HealthCheckResult.Healthy() | ||
| : HealthCheckResult.Unhealthy("Database connection is not configured.")); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.