forked from JasperFx/lamar
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Issue JasperFx#258 - Updated the integration tests cases to run a hea…
…lth check request using Microsoft's OOTB health check service which runs the health check registrations in parallel
- Loading branch information
Ryan Thomas
authored and
Ryan Thomas
committed
Jan 27, 2021
1 parent
795d80f
commit b956354
Showing
9 changed files
with
357 additions
and
52 deletions.
There are no files selected for viewing
This file contains 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
5 changes: 5 additions & 0 deletions
5
...AspNetCoreTests.Integration/MultiThreadProblem/App/HealthChecks/HealthCheckTestObjects.cs
This file contains 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,5 @@ | ||
using System; | ||
|
||
namespace Lamar.AspNetCoreTests.Integration.MultiThreadProblem.App.HealthChecks | ||
{ | ||
} |
107 changes: 107 additions & 0 deletions
107
...AspNetCoreTests.Integration/MultiThreadProblem/App/HealthChecks/IHealthCheckExtensions.cs
This file contains 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,107 @@ | ||
using Microsoft.AspNetCore.Builder; | ||
using Microsoft.AspNetCore.Diagnostics.HealthChecks; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.AspNetCore.Routing; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.Diagnostics.HealthChecks; | ||
using Newtonsoft.Json; | ||
using System; | ||
using System.Linq; | ||
using System.Text; | ||
|
||
namespace Lamar.AspNetCoreTests.Integration.MultiThreadProblem.App.HealthChecks | ||
{ | ||
public static class IHealthCheckExtensions | ||
{ | ||
public static IServiceCollection AddTestHealthChecks(this IServiceCollection services) | ||
{ | ||
services.AddHttpContextAccessor(); | ||
var healthCheckBuilder = services.AddHealthChecks(); | ||
|
||
healthCheckBuilder.AddDbContextCheck<Context>(); | ||
|
||
foreach (var index in Enumerable.Range(0, 20)) | ||
{ | ||
// Add multiple test health checks to ensure concurrent resolver access doesn't blow up | ||
healthCheckBuilder.AddTestHealthCheck($"TestHealthCheck{index}"); | ||
} | ||
|
||
return services; | ||
} | ||
|
||
public static IHealthChecksBuilder AddTestHealthCheck(this IHealthChecksBuilder builder, string registrationName) | ||
{ | ||
builder.Add(new HealthCheckRegistration( | ||
registrationName, | ||
(serviceProvider) => | ||
{ | ||
try | ||
{ | ||
// Get some different objects from the service provider to try and trigger any thread | ||
// safety issues in the container resolution logic | ||
var testObjects1 = serviceProvider.GetRequiredService<HealthCheckTestObjects1>(); | ||
var testObjects2 = serviceProvider.GetRequiredService<HealthCheckTestObjects2>(); | ||
return new SuccessHealthCheck(registrationName, testObjects1, testObjects2); | ||
} | ||
catch (Exception exc) | ||
{ | ||
return new SetupFailedHealthCheck(exc, registrationName); | ||
} | ||
}, | ||
HealthStatus.Unhealthy, | ||
default)); | ||
|
||
return builder; | ||
} | ||
|
||
public static string CreateHealthReportPlainText(string key, HealthReportEntry entry) | ||
{ | ||
var entryOutput = new StringBuilder($"{key}: {entry.Status} | {entry.Duration}\n"); | ||
if (entry.Tags?.Any() == true) | ||
{ | ||
entryOutput.Append("- Tags:"); | ||
entryOutput.Append(string.Join(", ", entry.Tags)); | ||
entryOutput.Append('\n'); | ||
} | ||
|
||
if (!string.IsNullOrWhiteSpace(entry.Description)) | ||
{ | ||
entryOutput.Append($"- Description: {entry.Description}\n\n"); | ||
} | ||
|
||
if (entry.Exception != null) | ||
{ | ||
entryOutput.Append($"- Exception: {entry.Exception}\n\n"); | ||
} | ||
|
||
if (entry.Data?.Count > 0) | ||
{ | ||
entryOutput.Append("- Data:\n"); | ||
foreach (var keyValuePair in entry.Data) | ||
{ | ||
entryOutput.Append($"\t{keyValuePair.Key}: {keyValuePair.Value}"); | ||
} | ||
entryOutput.Append('\n'); | ||
} | ||
|
||
return entryOutput.ToString(); | ||
} | ||
|
||
public static IEndpointRouteBuilder MapTestHealthChecks(this IEndpointRouteBuilder endpoints) | ||
{ | ||
endpoints.MapHealthChecks("/health", new HealthCheckOptions | ||
{ | ||
ResponseWriter = async (context, report) => | ||
{ | ||
var serializableReport = new SerializableHealthCheckResult(report); | ||
var resultString = JsonConvert.SerializeObject(serializableReport); | ||
context.Response.ContentType = "application/json"; | ||
await context.Response.WriteAsync(resultString).ConfigureAwait(false); | ||
} | ||
}); | ||
|
||
return endpoints; | ||
} | ||
} | ||
} |
53 changes: 53 additions & 0 deletions
53
...oreTests.Integration/MultiThreadProblem/App/HealthChecks/SerializableHealthCheckResult.cs
This file contains 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,53 @@ | ||
using Microsoft.Extensions.Diagnostics.HealthChecks; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
|
||
namespace Lamar.AspNetCoreTests.Integration.MultiThreadProblem.App.HealthChecks | ||
{ | ||
public class SerializableHealthCheckResult | ||
{ | ||
// Default constructor for json serialization / deserialization support | ||
public SerializableHealthCheckResult() { } | ||
|
||
public SerializableHealthCheckResult(HealthReport healthReport) | ||
{ | ||
_ = healthReport ?? throw new ArgumentNullException(nameof(healthReport)); | ||
|
||
Status = healthReport.Status; | ||
TotalDuration = healthReport.TotalDuration; | ||
|
||
if (healthReport.Entries != null) | ||
{ | ||
Entries = healthReport.Entries.Select(entry => new SerializableHealthCheckResultEntry(entry.Value, entry.Key)).ToList(); | ||
} | ||
} | ||
|
||
public List<SerializableHealthCheckResultEntry> Entries { get; set; } | ||
public HealthStatus Status { get; set; } | ||
public TimeSpan TotalDuration { get; set; } | ||
} | ||
|
||
public class SerializableHealthCheckResultEntry | ||
{ | ||
// Default constructor for json serialization / deserialization support | ||
public SerializableHealthCheckResultEntry() { } | ||
|
||
public SerializableHealthCheckResultEntry(HealthReportEntry entry, string name) | ||
{ | ||
Description = entry.Description; | ||
Duration = entry.Duration; | ||
Exception = entry.Exception?.ToString(); | ||
Name = name; | ||
Status = entry.Status; | ||
Tags = entry.Tags?.ToList(); | ||
} | ||
|
||
public string Description { get; set; } | ||
public TimeSpan Duration { get; set; } | ||
public string Exception { get; set; } | ||
public string Name { get; set; } | ||
public HealthStatus Status { get; set; } | ||
public List<string> Tags { get; set; } | ||
} | ||
} |
28 changes: 28 additions & 0 deletions
28
...AspNetCoreTests.Integration/MultiThreadProblem/App/HealthChecks/SetupFailedHealthCheck.cs
This file contains 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,28 @@ | ||
using Microsoft.Extensions.Diagnostics.HealthChecks; | ||
using System; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace Lamar.AspNetCoreTests.Integration.MultiThreadProblem.App.HealthChecks | ||
{ | ||
public class SetupFailedHealthCheck : IHealthCheck | ||
{ | ||
private readonly Exception _exception; | ||
private readonly string _registrationName; | ||
|
||
public SetupFailedHealthCheck(Exception exception, string registrationName) | ||
{ | ||
_exception = exception; | ||
_registrationName = registrationName; | ||
} | ||
|
||
public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) | ||
{ | ||
return Task.FromResult(new HealthCheckResult( | ||
HealthStatus.Unhealthy, | ||
description: $"An exception occurred while attempting to construct the health check for registration: {_registrationName}", | ||
exception: _exception | ||
)); | ||
} | ||
} | ||
} |
85 changes: 85 additions & 0 deletions
85
...mar.AspNetCoreTests.Integration/MultiThreadProblem/App/HealthChecks/SuccessHealthCheck.cs
This file contains 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,85 @@ | ||
using Microsoft.Extensions.Diagnostics.HealthChecks; | ||
using System; | ||
using System.Diagnostics; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace Lamar.AspNetCoreTests.Integration.MultiThreadProblem.App.HealthChecks | ||
{ | ||
public class SuccessHealthCheck : IHealthCheck | ||
{ | ||
private readonly string _registrationName; | ||
private readonly HealthCheckTestObjects1 _testObjects1; | ||
private readonly HealthCheckTestObjects2 _testObjects2; | ||
|
||
public SuccessHealthCheck(string registrationName, HealthCheckTestObjects1 testObjects1, HealthCheckTestObjects2 testObjects2) | ||
{ | ||
_registrationName = registrationName; | ||
_testObjects1 = testObjects1 ?? throw new ArgumentNullException(nameof(testObjects1)); | ||
_testObjects2 = testObjects2 ?? throw new ArgumentNullException(nameof(testObjects2)); | ||
} | ||
|
||
public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) | ||
{ | ||
return Task.FromResult(new HealthCheckResult( | ||
HealthStatus.Healthy, | ||
description: $"Health check successful for: {_registrationName}" | ||
)); | ||
} | ||
} | ||
|
||
public class HealthCheckTestObjects1 | ||
{ | ||
private readonly HealthCheckTestChild1 _child1; | ||
|
||
public HealthCheckTestObjects1(HealthCheckTestChild1 child1) | ||
{ | ||
_child1 = child1 ?? throw new ArgumentNullException(nameof(child1)); | ||
} | ||
} | ||
|
||
public class HealthCheckTestChild1 | ||
{ | ||
private readonly HealthCheckTestChild2 _child2; | ||
|
||
public HealthCheckTestChild1(HealthCheckTestChild2 child2) | ||
{ | ||
_child2 = child2 ?? throw new ArgumentNullException(nameof(child2)); | ||
} | ||
} | ||
|
||
public class HealthCheckTestChild2 | ||
{ | ||
private readonly HealthCheckTestChild3 _child3; | ||
|
||
public HealthCheckTestChild2(HealthCheckTestChild3 child3) | ||
{ | ||
_child3 = child3 ?? throw new ArgumentNullException(nameof(child3)); | ||
} | ||
} | ||
|
||
public class HealthCheckTestChild3 | ||
{ | ||
private readonly Context _context; | ||
|
||
public HealthCheckTestChild3(Context context) | ||
{ | ||
_context = context ?? throw new ArgumentNullException(nameof(context)); | ||
} | ||
} | ||
|
||
public class HealthCheckTestObjects2 | ||
{ | ||
public HealthCheckTestObjects2() | ||
{ | ||
// Simulate some cpu bound setup work... | ||
var stopWatch = new Stopwatch(); | ||
stopWatch.Start(); | ||
while (stopWatch.ElapsedMilliseconds < 5000) | ||
{ | ||
continue; | ||
} | ||
stopWatch.Stop(); | ||
} | ||
} | ||
} |
58 changes: 33 additions & 25 deletions
58
src/Lamar.AspNetCoreTests.Integration/MultiThreadProblem/App/LamarStartup.cs
This file contains 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 |
---|---|---|
@@ -1,36 +1,44 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Threading.Tasks; | ||
using Lamar.AspNetCoreTests.Integration.MultiThreadProblem.App.HealthChecks; | ||
using Microsoft.AspNetCore.Builder; | ||
using Microsoft.AspNetCore.Hosting; | ||
using Microsoft.AspNetCore.Mvc; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.Hosting; | ||
|
||
namespace Lamar.AspNetCoreTests.Integration.MultiThreadProblem.App | ||
{ | ||
public class LamarStartup | ||
{ | ||
public void ConfigureServices(IServiceCollection services) | ||
{ | ||
services.AddDbContext<Context>(); | ||
services.AddDbContext<SecondContext>(); | ||
public class LamarStartup | ||
{ | ||
public void ConfigureServices(IServiceCollection services) | ||
{ | ||
services.AddDbContext<Context>(); | ||
services.AddDbContext<SecondContext>(); | ||
|
||
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_3_0); | ||
} | ||
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_3_0); | ||
|
||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) | ||
{ | ||
app.UseRouting(); | ||
app.UseEndpoints(configure => configure.MapControllers()); | ||
} | ||
services.AddTestHealthChecks(); | ||
} | ||
|
||
public void ConfigureContainer(ServiceRegistry services) | ||
{ | ||
services.For<IBookService>().Use<BookService>().Transient(); | ||
services.For<IOtherService>().Use<OtherService>().Transient(); | ||
services.For<IContextFactory>().Use<ContextFactory>().Transient(); | ||
} | ||
} | ||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) | ||
{ | ||
app.UseRouting(); | ||
app.UseEndpoints(configure => | ||
{ | ||
configure.MapControllers(); | ||
configure.MapTestHealthChecks(); | ||
}); | ||
} | ||
|
||
public void ConfigureContainer(ServiceRegistry services) | ||
{ | ||
services.For<IBookService>().Use<BookService>().Transient(); | ||
services.For<IOtherService>().Use<OtherService>().Transient(); | ||
services.For<IContextFactory>().Use<ContextFactory>().Transient(); | ||
|
||
services.For<HealthCheckTestObjects1>().Use<HealthCheckTestObjects1>().Scoped(); | ||
services.For<HealthCheckTestObjects2>().Use<HealthCheckTestObjects2>().Scoped(); | ||
services.For<HealthCheckTestChild1>().Use<HealthCheckTestChild1>().Scoped(); | ||
services.For<HealthCheckTestChild2>().Use<HealthCheckTestChild2>().Scoped(); | ||
services.For<HealthCheckTestChild3>().Use<HealthCheckTestChild3>().Scoped(); | ||
} | ||
} | ||
} |
Oops, something went wrong.