Browse Source

Health checks.

pull/333/head
Sebastian Stehle 7 years ago
parent
commit
bfb03cd425
  1. 36
      src/Squidex.Domain.Apps.Entities/Apps/Diagnostics/OrleansAppsHealthCheck.cs
  2. 5
      src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsByNameIndexGrain.cs
  3. 2
      src/Squidex.Domain.Apps.Entities/Apps/Indexes/IAppsByNameIndex.cs
  4. 5
      src/Squidex.Domain.Apps.Entities/Assets/AssetQueryService.cs
  5. 5
      src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs
  6. 32
      src/Squidex.Infrastructure.GetEventStore/Diagnostics/GetEventStoreHealthCheck.cs
  7. 34
      src/Squidex.Infrastructure.MongoDb/Diagnostics/MongoDBHealthCheck.cs
  8. 42
      src/Squidex.Infrastructure/Diagnostics/GCHealthCheck.cs
  9. 14
      src/Squidex.Infrastructure/Diagnostics/GCHealthCheckOptions.cs
  10. 27
      src/Squidex.Infrastructure/Diagnostics/HealthCheckResult.cs
  11. 17
      src/Squidex.Infrastructure/Diagnostics/IHealthCheck.cs
  12. 33
      src/Squidex.Infrastructure/Diagnostics/OrleansHealthCheck.cs
  13. 3
      src/Squidex/AppServices.cs
  14. 1
      src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs
  15. 11
      src/Squidex/Config/Domain/EntitiesServices.cs
  16. 4
      src/Squidex/Config/Domain/EventStoreServices.cs
  17. 7
      src/Squidex/Config/Domain/InfrastructureServices.cs
  18. 4
      src/Squidex/Config/Domain/StoreServices.cs
  19. 8
      src/Squidex/Config/Web/WebExtensions.cs
  20. 4
      src/Squidex/Config/Web/WebServices.cs
  21. 83
      src/Squidex/Pipeline/Diagnostics/HealthCheckMiddleware.cs
  22. 14
      src/Squidex/Squidex.csproj
  23. 1
      src/Squidex/WebStartup.cs
  24. 2
      tests/RunCoverage.ps1
  25. 2
      tests/Squidex.Domain.Apps.Entities.Tests/Apps/Indexes/AppsByNameIndexGrainTests.cs
  26. 3
      tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetQueryServiceTests.cs
  27. 3
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs

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

@ -0,0 +1,36 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading;
using System.Threading.Tasks;
using Orleans;
using Squidex.Domain.Apps.Entities.Apps.Indexes;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Diagnostics;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Domain.Apps.Entities.Apps.Diagnostics
{
public sealed class OrleansAppsHealthCheck : IHealthCheck
{
private readonly IAppsByNameIndex index;
public OrleansAppsHealthCheck(IGrainFactory grainFactory)
{
Guard.NotNull(grainFactory, nameof(grainFactory));
index = grainFactory.GetGrain<IAppsByNameIndex>(SingleGrain.Id);
}
public async Task<HealthCheckResult> CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken))
{
await index.CountAsync();
return new HealthCheckResult(true);
}
}
}

5
src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsByNameIndexGrain.cs

@ -130,5 +130,10 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes
{ {
return Task.FromResult(state.Apps.Values.ToList()); return Task.FromResult(state.Apps.Values.ToList());
} }
public Task<long> CountAsync()
{
return Task.FromResult((long)state.Apps.Count);
}
} }
} }

2
src/Squidex.Domain.Apps.Entities/Apps/Indexes/IAppsByNameIndex.cs

@ -14,6 +14,8 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes
{ {
public interface IAppsByNameIndex : IGrainWithStringKey public interface IAppsByNameIndex : IGrainWithStringKey
{ {
Task<long> CountAsync();
Task<bool> ReserveAppAsync(Guid appId, string name); Task<bool> ReserveAppAsync(Guid appId, string name);
Task AddAppAsync(Guid appId, string name); Task AddAppAsync(Guid appId, string name);

5
src/Squidex.Domain.Apps.Entities/Assets/AssetQueryService.cs

@ -9,6 +9,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Microsoft.OData; using Microsoft.OData;
using Squidex.Domain.Apps.Core.Tags; using Squidex.Domain.Apps.Core.Tags;
using Squidex.Domain.Apps.Entities.Assets.Edm; using Squidex.Domain.Apps.Entities.Assets.Edm;
@ -26,14 +27,14 @@ namespace Squidex.Domain.Apps.Entities.Assets
private readonly IAssetRepository assetRepository; private readonly IAssetRepository assetRepository;
private readonly AssetOptions options; private readonly AssetOptions options;
public AssetQueryService(ITagService tagService, IAssetRepository assetRepository, AssetOptions options) public AssetQueryService(ITagService tagService, IAssetRepository assetRepository, IOptions<AssetOptions> options)
{ {
Guard.NotNull(tagService, nameof(tagService)); Guard.NotNull(tagService, nameof(tagService));
Guard.NotNull(options, nameof(options)); Guard.NotNull(options, nameof(options));
Guard.NotNull(assetRepository, nameof(assetRepository)); Guard.NotNull(assetRepository, nameof(assetRepository));
this.assetRepository = assetRepository; this.assetRepository = assetRepository;
this.options = options; this.options = options.Value;
this.tagService = tagService; this.tagService = tagService;
} }

5
src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs

@ -10,6 +10,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Security.Claims; using System.Security.Claims;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Microsoft.OData; using Microsoft.OData;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.ConvertContent; using Squidex.Domain.Apps.Core.ConvertContent;
@ -48,7 +49,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
IContentRepository contentRepository, IContentRepository contentRepository,
IContentVersionLoader contentVersionLoader, IContentVersionLoader contentVersionLoader,
IScriptEngine scriptEngine, IScriptEngine scriptEngine,
ContentOptions options, IOptions<ContentOptions> options,
EdmModelBuilder modelBuilder) EdmModelBuilder modelBuilder)
{ {
Guard.NotNull(appProvider, nameof(appProvider)); Guard.NotNull(appProvider, nameof(appProvider));
@ -62,7 +63,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
this.contentRepository = contentRepository; this.contentRepository = contentRepository;
this.contentVersionLoader = contentVersionLoader; this.contentVersionLoader = contentVersionLoader;
this.modelBuilder = modelBuilder; this.modelBuilder = modelBuilder;
this.options = options; this.options = options.Value;
this.scriptEngine = scriptEngine; this.scriptEngine = scriptEngine;
} }

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

@ -0,0 +1,32 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading;
using System.Threading.Tasks;
using EventStore.ClientAPI;
namespace Squidex.Infrastructure.Diagnostics
{
public sealed class GetEventStoreHealthCheck : IHealthCheck
{
private readonly IEventStoreConnection connection;
public GetEventStoreHealthCheck(IEventStoreConnection connection)
{
Guard.NotNull(connection, nameof(connection));
this.connection = connection;
}
public async Task<HealthCheckResult> CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken))
{
await connection.ReadEventAsync("test", 1, false);
return new HealthCheckResult(true, "Querying test event from event store.");
}
}
}

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

@ -0,0 +1,34 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading;
using System.Threading.Tasks;
using MongoDB.Driver;
namespace Squidex.Infrastructure.Diagnostics
{
public sealed class MongoDBHealthCheck : IHealthCheck
{
private readonly IMongoDatabase mongoDatabase;
public MongoDBHealthCheck(IMongoDatabase mongoDatabase)
{
Guard.NotNull(mongoDatabase, nameof(mongoDatabase));
this.mongoDatabase = mongoDatabase;
}
public async Task<HealthCheckResult> CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken))
{
var collectionNames = await mongoDatabase.ListCollectionNamesAsync(cancellationToken: cancellationToken);
var result = await collectionNames.AnyAsync(cancellationToken);
return new HealthCheckResult(result);
}
}
}

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

@ -0,0 +1,42 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
namespace Squidex.Infrastructure.Diagnostics
{
public sealed class GCHealthCheck : IHealthCheck
{
private readonly long threshold;
public GCHealthCheck(IOptions<GCHealthCheckOptions> options)
{
Guard.NotNull(options, nameof(options));
threshold = 1024 * 1024 * options.Value.Threshold;
}
public Task<HealthCheckResult> CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken))
{
var allocated = GC.GetTotalMemory(false);
var data = new Dictionary<string, object>()
{
{ "Allocated", allocated },
{ "Gen0Collections", GC.CollectionCount(0) },
{ "Gen1Collections", GC.CollectionCount(1) },
{ "Gen2Collections", GC.CollectionCount(2) },
};
return Task.FromResult(new HealthCheckResult(allocated < threshold, $"Reports degraded status if allocated bytes >= {threshold.ToReadableSize()}", data));
}
}
}

14
src/Squidex.Infrastructure/Diagnostics/GCHealthCheckOptions.cs

@ -0,0 +1,14 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Infrastructure.Diagnostics
{
public sealed class GCHealthCheckOptions
{
public long Threshold { get; set; } = 4 * 1024L;
}
}

27
src/Squidex.Infrastructure/Diagnostics/HealthCheckResult.cs

@ -0,0 +1,27 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
namespace Squidex.Infrastructure.Diagnostics
{
public sealed class HealthCheckResult
{
public bool IsValid { get; }
public string Description { get; }
public Dictionary<string, object> Data { get; }
public HealthCheckResult(bool isValid, string description = null, Dictionary<string, object> data = null)
{
IsValid = isValid;
Data = data;
Description = description;
}
}
}

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

@ -0,0 +1,17 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading;
using System.Threading.Tasks;
namespace Squidex.Infrastructure.Diagnostics
{
public interface IHealthCheck
{
Task<HealthCheckResult> CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken));
}
}

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

@ -0,0 +1,33 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading;
using System.Threading.Tasks;
using Orleans;
using Orleans.Runtime;
namespace Squidex.Infrastructure.Diagnostics
{
public sealed class OrleansHealthCheck : IHealthCheck
{
private readonly IManagementGrain managementGrain;
public OrleansHealthCheck(IGrainFactory grainFactory)
{
Guard.NotNull(grainFactory, nameof(grainFactory));
managementGrain = grainFactory.GetGrain<IManagementGrain>(0);
}
public async Task<HealthCheckResult> CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken))
{
var activationCount = await managementGrain.GetTotalActivationCount();
return new HealthCheckResult(activationCount > 0, "Orleans must have at least one activation.");
}
}
}

3
src/Squidex/AppServices.cs

@ -18,6 +18,7 @@ using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Extensions.Actions.Twitter; using Squidex.Extensions.Actions.Twitter;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Diagnostics;
namespace Squidex namespace Squidex
{ {
@ -54,6 +55,8 @@ namespace Squidex
config.GetSection("mode")); config.GetSection("mode"));
services.Configure<TwitterOptions>( services.Configure<TwitterOptions>(
config.GetSection("twitter")); config.GetSection("twitter"));
services.Configure<GCHealthCheckOptions>(
config.GetSection("healthz:gc"));
services.Configure<MyContentsControllerOptions>( services.Configure<MyContentsControllerOptions>(
config.GetSection("contentsController")); config.GetSection("contentsController"));

1
src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs

@ -9,7 +9,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using IdentityServer4.Models;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using NodaTime; using NodaTime;
using Squidex.Areas.Api.Controllers.Rules.Models; using Squidex.Areas.Api.Controllers.Rules.Models;

11
src/Squidex/Config/Domain/EntitiesServices.cs

@ -19,6 +19,7 @@ using Squidex.Domain.Apps.Core.Tags;
using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.Apps.Diagnostics;
using Squidex.Domain.Apps.Entities.Apps.Indexes; using Squidex.Domain.Apps.Entities.Apps.Indexes;
using Squidex.Domain.Apps.Entities.Apps.Templates; using Squidex.Domain.Apps.Entities.Apps.Templates;
using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Assets;
@ -40,6 +41,7 @@ using Squidex.Domain.Apps.Entities.Schemas.Indexes;
using Squidex.Domain.Apps.Entities.Tags; using Squidex.Domain.Apps.Entities.Tags;
using Squidex.Infrastructure.Assets; using Squidex.Infrastructure.Assets;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Diagnostics;
using Squidex.Infrastructure.Migrations; using Squidex.Infrastructure.Migrations;
using Squidex.Pipeline; using Squidex.Pipeline;
using Squidex.Pipeline.CommandMiddlewares; using Squidex.Pipeline.CommandMiddlewares;
@ -67,15 +69,9 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<AppProvider>() services.AddSingletonAs<AppProvider>()
.As<IAppProvider>(); .As<IAppProvider>();
services.AddSingletonAs(c => c.GetRequiredService<IOptions<AssetOptions>>().Value)
.AsSelf();
services.AddSingletonAs<AssetQueryService>() services.AddSingletonAs<AssetQueryService>()
.As<IAssetQueryService>(); .As<IAssetQueryService>();
services.AddSingletonAs(c => c.GetRequiredService<IOptions<ContentOptions>>().Value)
.AsSelf();
services.AddSingletonAs<ContentQueryService>() services.AddSingletonAs<ContentQueryService>()
.As<IContentQueryService>(); .As<IContentQueryService>();
@ -91,6 +87,9 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<SchemaHistoryEventsCreator>() services.AddSingletonAs<SchemaHistoryEventsCreator>()
.As<IHistoryEventsCreator>(); .As<IHistoryEventsCreator>();
services.AddSingletonAs<OrleansAppsHealthCheck>()
.As<IHealthCheck>();
services.AddSingletonAs<RolePermissionsProvider>() services.AddSingletonAs<RolePermissionsProvider>()
.AsSelf(); .AsSelf();

4
src/Squidex/Config/Domain/EventStoreServices.cs

@ -11,6 +11,7 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using MongoDB.Driver; using MongoDB.Driver;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Diagnostics;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.EventSourcing.Grains; using Squidex.Infrastructure.EventSourcing.Grains;
using Squidex.Infrastructure.States; using Squidex.Infrastructure.States;
@ -46,6 +47,9 @@ namespace Squidex.Config.Domain
var connection = EventStoreConnection.Create(eventStoreConfiguration); var connection = EventStoreConnection.Create(eventStoreConfiguration);
services.AddSingletonAs(c => new GetEventStoreHealthCheck(connection))
.As<IHealthCheck>();
services.AddSingletonAs(c => new GetEventStore(connection, eventStorePrefix, eventStoreProjectionHost)) services.AddSingletonAs(c => new GetEventStore(connection, eventStorePrefix, eventStoreProjectionHost))
.As<IInitializable>() .As<IInitializable>()
.As<IEventStore>(); .As<IEventStore>();

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

@ -13,6 +13,7 @@ using Microsoft.Extensions.DependencyInjection;
using NodaTime; using NodaTime;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Caching; using Squidex.Infrastructure.Caching;
using Squidex.Infrastructure.Diagnostics;
using Squidex.Infrastructure.UsageTracking; using Squidex.Infrastructure.UsageTracking;
#pragma warning disable RECS0092 // Convert field to readonly #pragma warning disable RECS0092 // Convert field to readonly
@ -35,6 +36,12 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<AsyncLocalCache>() services.AddSingletonAs<AsyncLocalCache>()
.As<ILocalCache>(); .As<ILocalCache>();
services.AddSingletonAs<GCHealthCheck>()
.As<IHealthCheck>();
services.AddSingletonAs<OrleansHealthCheck>()
.As<IHealthCheck>();
services.AddSingletonAs<HttpContextAccessor>() services.AddSingletonAs<HttpContextAccessor>()
.As<IHttpContextAccessor>(); .As<IHttpContextAccessor>();

4
src/Squidex/Config/Domain/StoreServices.cs

@ -28,6 +28,7 @@ using Squidex.Domain.Users;
using Squidex.Domain.Users.MongoDb; using Squidex.Domain.Users.MongoDb;
using Squidex.Domain.Users.MongoDb.Infrastructure; using Squidex.Domain.Users.MongoDb.Infrastructure;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Diagnostics;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Migrations; using Squidex.Infrastructure.Migrations;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
@ -60,6 +61,9 @@ namespace Squidex.Config.Domain
services.AddSingletonAs(mongoDatabase) services.AddSingletonAs(mongoDatabase)
.As<IMongoDatabase>(); .As<IMongoDatabase>();
services.AddSingletonAs<MongoDBHealthCheck>()
.As<IHealthCheck>();
services.AddSingletonAs<MongoXmlRepository>() services.AddSingletonAs<MongoXmlRepository>()
.As<IXmlRepository>() .As<IXmlRepository>()
.As<IInitializable>(); .As<IInitializable>();

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

@ -8,6 +8,7 @@
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.HttpOverrides;
using Squidex.Pipeline; using Squidex.Pipeline;
using Squidex.Pipeline.Diagnostics;
namespace Squidex.Config.Web namespace Squidex.Config.Web
{ {
@ -27,6 +28,13 @@ namespace Squidex.Config.Web
return app; return app;
} }
public static IApplicationBuilder UseMyHealthCheck(this IApplicationBuilder app)
{
app.Map("/healthz", builder => builder.UseMiddleware<HealthCheckMiddleware>());
return app;
}
public static void UseMyCors(this IApplicationBuilder app) public static void UseMyCors(this IApplicationBuilder app)
{ {
app.UseCors(builder => builder app.UseCors(builder => builder

4
src/Squidex/Config/Web/WebServices.cs

@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Squidex.Config.Domain; using Squidex.Config.Domain;
using Squidex.Pipeline; using Squidex.Pipeline;
using Squidex.Pipeline.Diagnostics;
namespace Squidex.Config.Web namespace Squidex.Config.Web
{ {
@ -25,6 +26,9 @@ namespace Squidex.Config.Web
services.AddSingletonAs<AppResolver>() services.AddSingletonAs<AppResolver>()
.AsSelf(); .AsSelf();
services.AddSingletonAs<HealthCheckMiddleware>()
.AsSelf();
services.AddSingletonAs<EnforceHttpsMiddleware>() services.AddSingletonAs<EnforceHttpsMiddleware>()
.AsSelf(); .AsSelf();

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

@ -0,0 +1,83 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Diagnostics;
namespace Squidex.Pipeline.Diagnostics
{
public sealed class HealthCheckMiddleware : IMiddleware
{
private const string Suffix = "HealthCheck";
private static readonly TimeSpan Timeout = TimeSpan.FromSeconds(2);
private readonly Dictionary<string, IHealthCheck> healthChecks;
private readonly JsonSerializerSettings serializerSettings;
public HealthCheckMiddleware(IEnumerable<IHealthCheck> healthChecks, JsonSerializerSettings serializerSettings)
{
Guard.NotNull(healthChecks, nameof(healthChecks));
Guard.NotNull(serializerSettings, nameof(serializerSettings));
this.healthChecks = healthChecks.ToDictionary(GetName);
this.serializerSettings = serializerSettings;
}
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
using (var cts = new CancellationTokenSource(Timeout))
{
var checks = await Task.WhenAll(healthChecks.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.IsValid))
{
context.Response.StatusCode = 503;
}
var response = checks.ToDictionary(x => x.Name, x => x.Result);
await context.Response.WriteAsync(JsonConvert.SerializeObject(new { status = response }, Formatting.Indented, serializerSettings));
}
}
private static string GetName(IHealthCheck check)
{
var name = check.GetType().Name.ToCamelCase();
if (name.EndsWith(Suffix, StringComparison.OrdinalIgnoreCase))
{
name = name.Substring(0, name.Length - Suffix.Length);
}
return name;
}
private async Task<(string Name, HealthCheckResult Result)> MakeHealthCheckAsync(string name, IHealthCheck check, CancellationToken ct)
{
try
{
var result = await check.CheckHealthAsync(ct);
return (name, result);
}
catch
{
return (name, new HealthCheckResult(false));
}
}
}
}

14
src/Squidex/Squidex.csproj

@ -73,19 +73,19 @@
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.1.1" /> <PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.1.1" />
<PackageReference Include="Microsoft.Data.Edm" Version="5.8.4" /> <PackageReference Include="Microsoft.Data.Edm" Version="5.8.4" />
<PackageReference Include="Microsoft.OData.Core" Version="7.5.1" /> <PackageReference Include="Microsoft.OData.Core" Version="7.5.1" />
<PackageReference Include="Microsoft.Orleans.Client" Version="2.1.0" /> <PackageReference Include="Microsoft.Orleans.Client" Version="2.1.2" />
<PackageReference Include="Microsoft.Orleans.Core" Version="2.1.0" /> <PackageReference Include="Microsoft.Orleans.Core" Version="2.1.2" />
<PackageReference Include="Microsoft.Orleans.Core.Abstractions" Version="2.1.0" /> <PackageReference Include="Microsoft.Orleans.Core.Abstractions" Version="2.1.2" />
<PackageReference Include="Microsoft.Orleans.OrleansRuntime" Version="2.1.0" /> <PackageReference Include="Microsoft.Orleans.OrleansRuntime" Version="2.1.2" />
<PackageReference Include="MongoDB.Driver" Version="2.7.0" /> <PackageReference Include="MongoDB.Driver" Version="2.7.0" />
<PackageReference Include="NJsonSchema" Version="9.11.0" /> <PackageReference Include="NJsonSchema" Version="9.12.0" />
<PackageReference Include="NodaTime.Serialization.JsonNet" Version="2.0.0" /> <PackageReference Include="NodaTime.Serialization.JsonNet" Version="2.0.0" />
<PackageReference Include="NSwag.AspNetCore" Version="11.20.1" /> <PackageReference Include="NSwag.AspNetCore" Version="11.20.1" />
<PackageReference Include="OpenCover" Version="4.6.519" /> <PackageReference Include="OpenCover" Version="4.6.519" />
<PackageReference Include="Orleans.Providers.MongoDB" Version="2.0.1" /> <PackageReference Include="Orleans.Providers.MongoDB" Version="2.0.1" />
<PackageReference Include="OrleansDashboard" Version="2.1.2" /> <PackageReference Include="OrleansDashboard" Version="2.1.3" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" /> <PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="ReportGenerator" Version="3.1.2" /> <PackageReference Include="ReportGenerator" Version="4.0.2" />
<PackageReference Include="StackExchange.Redis.StrongName" Version="1.2.6" /> <PackageReference Include="StackExchange.Redis.StrongName" Version="1.2.6" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" PrivateAssets="all" /> <PackageReference Include="StyleCop.Analyzers" Version="1.0.2" PrivateAssets="all" />
<PackageReference Include="System.Linq" Version="4.3.0" /> <PackageReference Include="System.Linq" Version="4.3.0" />

1
src/Squidex/WebStartup.cs

@ -41,6 +41,7 @@ namespace Squidex
{ {
app.ApplicationServices.LogConfiguration(); app.ApplicationServices.LogConfiguration();
app.UseMyHealthCheck();
app.UseMyTracking(); app.UseMyTracking();
app.UseMyLocalCache(); app.UseMyLocalCache();
app.UseMyCors(); app.UseMyCors();

2
tests/RunCoverage.ps1

@ -76,6 +76,6 @@ if ($all -Or $web) {
-oldStyle -oldStyle
} }
&"$folderHome\.nuget\packages\ReportGenerator\3.1.2\tools\ReportGenerator.exe" ` &"$folderHome\.nuget\packages\ReportGenerator\4.0.2\tools\ReportGenerator.exe" `
-reports:"$folderWorking\$folderReports\*.xml" ` -reports:"$folderWorking\$folderReports\*.xml" `
-targetdir:"$folderWorking\$folderReports\Output" -targetdir:"$folderWorking\$folderReports\Output"

2
tests/Squidex.Domain.Apps.Entities.Tests/Apps/Indexes/AppsByNameIndexGrainTests.cs

@ -146,6 +146,8 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes
Assert.Equal(new List<Guid> { appId1, appId2 }, await sut.GetAppIdsAsync()); Assert.Equal(new List<Guid> { appId1, appId2 }, await sut.GetAppIdsAsync());
Assert.Equal(2, await sut.CountAsync());
A.CallTo(() => persistence.WriteSnapshotAsync(A<AppsByNameIndexGrain.State>.Ignored)) A.CallTo(() => persistence.WriteSnapshotAsync(A<AppsByNameIndexGrain.State>.Ignored))
.MustHaveHappened(); .MustHaveHappened();
} }

3
tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetQueryServiceTests.cs

@ -10,6 +10,7 @@ using System.Collections.Generic;
using System.Security.Claims; using System.Security.Claims;
using System.Threading.Tasks; using System.Threading.Tasks;
using FakeItEasy; using FakeItEasy;
using Microsoft.Extensions.Options;
using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Tags; using Squidex.Domain.Apps.Core.Tags;
using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps;
@ -49,7 +50,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
["id3"] = "name3" ["id3"] = "name3"
}); });
sut = new AssetQueryService(tagService, assetRepository, new AssetOptions()); sut = new AssetQueryService(tagService, assetRepository, Options.Create(new AssetOptions()));
} }
[Fact] [Fact]

3
tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs

@ -11,6 +11,7 @@ using System.Linq;
using System.Security.Claims; using System.Security.Claims;
using System.Threading.Tasks; using System.Threading.Tasks;
using FakeItEasy; using FakeItEasy;
using Microsoft.Extensions.Options;
using Microsoft.OData; using Microsoft.OData;
using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
@ -69,7 +70,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
context = new ContentQueryContext(QueryContext.Create(app, user)); context = new ContentQueryContext(QueryContext.Create(app, user));
sut = new ContentQueryService(appProvider, contentRepository, contentVersionLoader, scriptEngine, new ContentOptions(), modelBuilder); sut = new ContentQueryService(appProvider, contentRepository, contentVersionLoader, scriptEngine, Options.Create(new ContentOptions()), modelBuilder);
} }
[Fact] [Fact]

Loading…
Cancel
Save