Browse Source

Readiness and liveness check.

pull/335/head
Sebastian Stehle 8 years ago
parent
commit
852b0400a5
  1. 6
      src/Squidex.Domain.Apps.Entities/Apps/Diagnostics/OrleansAppsHealthCheck.cs
  2. 6
      src/Squidex.Infrastructure.GetEventStore/Diagnostics/GetEventStoreHealthCheck.cs
  3. 6
      src/Squidex.Infrastructure.MongoDb/Diagnostics/MongoDBHealthCheck.cs
  4. 5
      src/Squidex.Infrastructure/Diagnostics/GCHealthCheck.cs
  5. 16
      src/Squidex.Infrastructure/Diagnostics/HealthCheckScopes.cs
  6. 3
      src/Squidex.Infrastructure/Diagnostics/IHealthCheck.cs
  7. 6
      src/Squidex.Infrastructure/Diagnostics/OrleansHealthCheck.cs
  8. 9
      src/Squidex/Config/Domain/InfrastructureServices.cs
  9. 17
      src/Squidex/Config/Web/WebExtensions.cs
  10. 30
      src/Squidex/Pipeline/Diagnostics/HealthCheckMiddleware.cs

6
src/Squidex.Domain.Apps.Entities/Apps/Diagnostics/OrleansAppsHealthCheck.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Orleans;
@ -19,6 +20,11 @@ namespace Squidex.Domain.Apps.Entities.Apps.Diagnostics
{
private readonly IAppsByNameIndex index;
public IEnumerable<string> Scopes
{
get { yield return HealthCheckScopes.Cluster; }
}
public OrleansAppsHealthCheck(IGrainFactory grainFactory)
{
Guard.NotNull(grainFactory, nameof(grainFactory));

6
src/Squidex.Infrastructure.GetEventStore/Diagnostics/GetEventStoreHealthCheck.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using EventStore.ClientAPI;
@ -15,6 +16,11 @@ namespace Squidex.Infrastructure.Diagnostics
{
private readonly IEventStoreConnection connection;
public IEnumerable<string> Scopes
{
get { yield return HealthCheckScopes.Node; }
}
public GetEventStoreHealthCheck(IEventStoreConnection connection)
{
Guard.NotNull(connection, nameof(connection));

6
src/Squidex.Infrastructure.MongoDb/Diagnostics/MongoDBHealthCheck.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MongoDB.Driver;
@ -15,6 +16,11 @@ namespace Squidex.Infrastructure.Diagnostics
{
private readonly IMongoDatabase mongoDatabase;
public IEnumerable<string> Scopes
{
get { yield return HealthCheckScopes.Node; }
}
public MongoDBHealthCheck(IMongoDatabase mongoDatabase)
{
Guard.NotNull(mongoDatabase, nameof(mongoDatabase));

5
src/Squidex.Infrastructure/Diagnostics/GCHealthCheck.cs

@ -17,6 +17,11 @@ namespace Squidex.Infrastructure.Diagnostics
{
private readonly long threshold;
public IEnumerable<string> Scopes
{
get { yield return HealthCheckScopes.Node; }
}
public GCHealthCheck(IOptions<GCHealthCheckOptions> options)
{
Guard.NotNull(options, nameof(options));

16
src/Squidex.Infrastructure/Diagnostics/HealthCheckScopes.cs

@ -0,0 +1,16 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Infrastructure.Diagnostics
{
public static class HealthCheckScopes
{
public const string Any = "*";
public const string Node = "node";
public const string Cluster = "cluster";
}
}

3
src/Squidex.Infrastructure/Diagnostics/IHealthCheck.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@ -12,6 +13,8 @@ namespace Squidex.Infrastructure.Diagnostics
{
public interface IHealthCheck
{
IEnumerable<string> Scopes { get; }
Task<HealthCheckResult> CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken));
}
}

6
src/Squidex.Infrastructure/Diagnostics/OrleansHealthCheck.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Orleans;
@ -16,6 +17,11 @@ namespace Squidex.Infrastructure.Diagnostics
{
private readonly IManagementGrain managementGrain;
public IEnumerable<string> Scopes
{
get { yield return HealthCheckScopes.Cluster; }
}
public OrleansHealthCheck(IGrainFactory grainFactory)
{
Guard.NotNull(grainFactory, nameof(grainFactory));

9
src/Squidex/Config/Domain/InfrastructureServices.cs

@ -42,6 +42,9 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<GCHealthCheck>()
.As<IHealthCheck>();
services.AddSingletonAs<OrleansHealthCheck>()
.As<IHealthCheck>();
services.AddSingletonAs<HttpContextAccessor>()
.As<IHttpContextAccessor>();
@ -51,12 +54,12 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<DefaultUserResolver>()
.As<IUserResolver>();
services.AddSingletonAs<DefaultXmlRepository>()
.As<IXmlRepository>();
services.AddSingletonAs<AssetUserPictureStore>()
.As<IUserPictureStore>();
services.AddSingletonAs<DefaultXmlRepository>()
.As<IXmlRepository>();
services.AddTransient(typeof(Lazy<>), typeof(Lazier<>));
}
}

17
src/Squidex/Config/Web/WebExtensions.cs

@ -5,8 +5,10 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.HttpOverrides;
using Squidex.Infrastructure.Diagnostics;
using Squidex.Pipeline;
using Squidex.Pipeline.Diagnostics;
using Squidex.Pipeline.Robots;
@ -31,7 +33,20 @@ namespace Squidex.Config.Web
public static IApplicationBuilder UseMyHealthCheck(this IApplicationBuilder app)
{
app.Map("/healthz", builder => builder.UseMiddleware<HealthCheckMiddleware>());
app.Map("/readiness", builder =>
{
builder.UseMiddleware<HealthCheckMiddleware>(HealthCheckScopes.Any);
});
app.Map("/healthz", builder =>
{
builder.UseMiddleware<HealthCheckMiddleware>(HealthCheckScopes.Node);
});
app.Map("/cluster-healthz", builder =>
{
builder.UseMiddleware<HealthCheckMiddleware>(HealthCheckScopes.Cluster);
});
return app;
}

30
src/Squidex/Pipeline/Diagnostics/HealthCheckMiddleware.cs

@ -17,40 +17,46 @@ using Squidex.Infrastructure.Diagnostics;
namespace Squidex.Pipeline.Diagnostics
{
public sealed class HealthCheckMiddleware : IMiddleware
public sealed class HealthCheckMiddleware
{
private const string Suffix = "HealthCheck";
private static readonly TimeSpan Timeout = TimeSpan.FromSeconds(2);
private readonly Dictionary<string, IHealthCheck> healthChecks;
private readonly JsonSerializerSettings serializerSettings;
private readonly RequestDelegate next;
private readonly List<string> scopes;
public HealthCheckMiddleware(IEnumerable<IHealthCheck> healthChecks, JsonSerializerSettings serializerSettings)
public HealthCheckMiddleware(IEnumerable<IHealthCheck> healthChecks, JsonSerializerSettings serializerSettings, RequestDelegate next, string scopes)
{
Guard.NotNull(healthChecks, nameof(healthChecks));
Guard.NotNull(serializerSettings, nameof(serializerSettings));
this.healthChecks = healthChecks.ToDictionary(GetName);
this.next = next;
this.scopes = SplitScopes(scopes);
this.serializerSettings = serializerSettings;
}
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
public async Task Invoke(HttpContext context)
{
if (CanServeRequest(context.Request))
{
using (var cts = new CancellationTokenSource(Timeout))
{
var checks = await Task.WhenAll(healthChecks.Select(x => MakeHealthCheckAsync(x.Key, x.Value, cts.Token)));
var matchingChecks = healthChecks.Where(x => CanUseCheck(x.Value));
var results = await Task.WhenAll(matchingChecks.Select(x => MakeHealthCheckAsync(x.Key, x.Value, cts.Token)));
context.Response.StatusCode = 200;
context.Response.Headers.Add("Content-Type", "application/json");
if (checks.Any(x => !x.Result.IsHealthy))
if (results.Any(x => !x.Result.IsHealthy))
{
context.Response.StatusCode = 503;
}
var response = checks.ToDictionary(x => x.Name, x => x.Result);
var response = results.ToDictionary(x => x.Name, x => x.Result);
await context.Response.WriteAsync(JsonConvert.SerializeObject(new { status = response }, Formatting.Indented, serializerSettings));
}
@ -61,11 +67,21 @@ namespace Squidex.Pipeline.Diagnostics
}
}
private static bool CanServeRequest(HttpRequest request)
private bool CanUseCheck(IHealthCheck check)
{
return scopes.Count == 0 || check.Scopes.Intersect(scopes).Any();
}
private bool CanServeRequest(HttpRequest request)
{
return HttpMethods.IsGet(request.Method) && (request.Path == "/" || string.IsNullOrEmpty(request.Path));
}
private static List<string> SplitScopes(string scopes)
{
return scopes.Split(",").Where(x => x != "*").ToList();
}
private static string GetName(IHealthCheck check)
{
var name = check.GetType().Name.ToCamelCase();

Loading…
Cancel
Save