Skip to content
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

Neo4j health checks #2370

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
14 changes: 14 additions & 0 deletions AspNetCore.Diagnostics.HealthChecks.sln
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HealthChecks.ClickHouse", "
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HealthChecks.ClickHouse.Tests", "test\HealthChecks.ClickHouse.Tests\HealthChecks.ClickHouse.Tests.csproj", "{2FB5CB9F-F870-48DE-BD1D-306AE86A67CA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HealthChecks.Neo4jClient", "src\HealthChecks.Neo4jClient\HealthChecks.Neo4jClient.csproj", "{3B70557A-BC3F-4CEF-8EAB-C57CE07DCEC5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HealthChecks.Neo4jClient.Tests", "test\HealthChecks.Neo4jClient.Tests\HealthChecks.Neo4jClient.Tests.csproj", "{64624A04-52CF-4558-811A-4FDD8E8FD4E6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -905,6 +909,14 @@ Global
{2FB5CB9F-F870-48DE-BD1D-306AE86A67CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2FB5CB9F-F870-48DE-BD1D-306AE86A67CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2FB5CB9F-F870-48DE-BD1D-306AE86A67CA}.Release|Any CPU.Build.0 = Release|Any CPU
{3B70557A-BC3F-4CEF-8EAB-C57CE07DCEC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3B70557A-BC3F-4CEF-8EAB-C57CE07DCEC5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3B70557A-BC3F-4CEF-8EAB-C57CE07DCEC5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3B70557A-BC3F-4CEF-8EAB-C57CE07DCEC5}.Release|Any CPU.Build.0 = Release|Any CPU
{64624A04-52CF-4558-811A-4FDD8E8FD4E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{64624A04-52CF-4558-811A-4FDD8E8FD4E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{64624A04-52CF-4558-811A-4FDD8E8FD4E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{64624A04-52CF-4558-811A-4FDD8E8FD4E6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -1054,6 +1066,8 @@ Global
{44BB97EE-88DB-4C9B-8195-2C6D889AE391} = {FF4414C2-8863-4ADA-8A1D-4B9F25C361FE}
{96E2B0A3-02BD-456B-8888-4D96DABA99EB} = {2A3FD988-2BB8-43CF-B3A2-B70E648259D4}
{2FB5CB9F-F870-48DE-BD1D-306AE86A67CA} = {FF4414C2-8863-4ADA-8A1D-4B9F25C361FE}
{3B70557A-BC3F-4CEF-8EAB-C57CE07DCEC5} = {2A3FD988-2BB8-43CF-B3A2-B70E648259D4}
{64624A04-52CF-4558-811A-4FDD8E8FD4E6} = {FF4414C2-8863-4ADA-8A1D-4B9F25C361FE}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {2B8C62A1-11B6-469F-874C-A02443256568}
Expand Down
5 changes: 3 additions & 2 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
<PackageVersion Include="MySqlConnector" Version="2.3.1" />
<PackageVersion Include="MySqlConnector.DependencyInjection" Version="2.3.1" />
<PackageVersion Include="NATS.Net" Version="2.5.4" />
<PackageVersion Include="Neo4jClient" Version="5.1.20" />
<PackageVersion Include="Npgsql" Version="8.0.3" />
<PackageVersion Include="Npgsql.DependencyInjection" Version="8.0.1" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.11" />
Expand Down Expand Up @@ -111,10 +112,10 @@
<PackageVersion Include="Testcontainers.MySql" Version="$(TestcontainersVersion)" />
<PackageVersion Include="Testcontainers.Redis" Version="$(TestcontainersVersion)" />
<PackageVersion Include="Testcontainers.RabbitMq" Version="$(TestcontainersVersion)" />
<PackageVersion Include="xunit" Version="2.9.2" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
</ItemGroup>
<ItemGroup Condition="'$(IsPackable)' == 'true'">
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
</ItemGroup>
</Project>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using HealthChecks.Neo4jClient;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Neo4jClient;

namespace Microsoft.Extensions.DependencyInjection;

/// <summary>
/// Extension methods to configure <see cref="Neo4jClientHealthCheck"/>.
/// </summary>
public static class Neo4jClientHealthCheckBuilderExtensions
{
private const string HEALTH_CHECK_NAME = "neo4j";

/// <summary>
/// Add a health check for Neo4j databases.
/// </summary>
/// <param name="builder">The extension for <see cref="IHealthChecksBuilder"/>.</param>
/// <param name="graphClientFactory">A factory to build <see cref="IGraphClient"/>.</param>
/// <param name="name">The health check name. Optional. If <c>null</c> the type name 'neo4j' will be used for the name.</param>
/// <param name="failureStatus">
/// The <see cref="HealthStatus"/> that should be reported when the health check fails. Optional. If <c>null</c> then
/// the default status of <see cref="HealthStatus.Unhealthy"/> will be reported.
/// </param>
/// <param name="tags">A list of tags that can be used to filter sets of health checks. Optional.</param>
/// <param name="timeout">An optional <see cref="TimeSpan"/> representing the timeout of the check.</param>
/// <returns>The <see cref="IHealthChecksBuilder"/> <paramref name="builder"/>.</returns>
public static IHealthChecksBuilder AddNeo4jClient(
this IHealthChecksBuilder builder,
Func<IServiceProvider, IGraphClient> graphClientFactory,
string? name = default,
HealthStatus? failureStatus = default,
IEnumerable<string>? tags = default,
TimeSpan? timeout = default)
{
var healthCheckRegistration = new HealthCheckRegistration(
name ?? HEALTH_CHECK_NAME,
sp =>
{
var graphClient = graphClientFactory(sp);
var options = new Neo4jClientHealthCheckOptions(graphClient);

return new Neo4jClientHealthCheck(options);
},
failureStatus,
tags,
timeout
);

return builder.Add(healthCheckRegistration);
}

/// <summary>
/// Add a health check for Neo4j databases.
/// </summary>
/// <param name="builder">The extension for <see cref="IHealthChecksBuilder"/>.</param>
/// <param name="healthCheckOptions"><see cref="Neo4jClientHealthCheckOptions"/> instance for health check.</param>
/// <param name="name">The health check name. Optional. If <c>null</c> the type name 'neo4j' will be used for the name.</param>
/// <param name="failureStatus">
/// The <see cref="HealthStatus"/> that should be reported when the health check fails. Optional. If <c>null</c> then
/// the default status of <see cref="HealthStatus.Unhealthy"/> will be reported.
/// </param>
/// <param name="tags">A list of tags that can be used to filter sets of health checks. Optional.</param>
/// <param name="timeout">An optional <see cref="TimeSpan"/> representing the timeout of the check.</param>
/// <returns>The <see cref="IHealthChecksBuilder"/> <paramref name="builder"/>.</returns>
public static IHealthChecksBuilder AddNeo4jClient(
this IHealthChecksBuilder builder,
Neo4jClientHealthCheckOptions healthCheckOptions,
string? name = default,
HealthStatus? failureStatus = default,
IEnumerable<string>? tags = default,
TimeSpan? timeout = default)
{
var healthCheckRegistration = new HealthCheckRegistration(
name ?? HEALTH_CHECK_NAME,
_ => new Neo4jClientHealthCheck(healthCheckOptions),
failureStatus,
tags,
timeout
);

return builder.Add(healthCheckRegistration);
}
}
11 changes: 11 additions & 0 deletions src/HealthChecks.Neo4jClient/HealthChecks.Neo4jClient.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Neo4jClient" />
</ItemGroup>

</Project>
46 changes: 46 additions & 0 deletions src/HealthChecks.Neo4jClient/Neo4jClientHealthCheck.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Neo4jClient;

namespace HealthChecks.Neo4jClient;

/// <summary>
/// A health check for Neo4j databases.
/// </summary>
public class Neo4jClientHealthCheck : IHealthCheck
{
private readonly Neo4jClientHealthCheckOptions _options;

/// <summary>
/// Creates an instance with the options passed to it
/// </summary>
public Neo4jClientHealthCheck(Neo4jClientHealthCheckOptions options)
{
_options = Guard.ThrowIfNull(options);
}

///<inheritdoc/>
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
try
{
_options.GraphClient ??= new BoltGraphClient(new Uri(_options.Host),
_options.Username,
_options.Password,
_options.Realm,
_options.EncryptionLevel,
_options.SerializeNullValues,
_options.UseDriverDataTypes);


var graphClient = _options.GraphClient;

await graphClient.ConnectAsync().ConfigureAwait(false);

return HealthCheckResult.Healthy();
}
catch (Exception ex)
{
return HealthCheckResult.Unhealthy(ex.Message, ex);
}
}
}
74 changes: 74 additions & 0 deletions src/HealthChecks.Neo4jClient/Neo4jClientHealthCheckOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using Neo4j.Driver;
using Neo4jClient;

namespace HealthChecks.Neo4jClient;

/// <summary>
/// Options for <see cref="Neo4jClientHealthCheck"/>.
/// </summary>
public class Neo4jClientHealthCheckOptions
{
/// <summary>
/// Client for connecting to a database.
/// </summary>
public IGraphClient? GraphClient { get; set; }

/// <summary>
/// Host that will be used to connect to the database.
/// </summary>
public string? Host { get; set; }

/// <summary>
/// Username that will be used to connect to the database using the <see cref="IGraphClient"/>.
/// </summary>
public string? Username { get; set; }

/// <summary>
/// Password that will be used to connect to the database using the <see cref="IGraphClient"/>.
/// </summary>
public string? Password { get; set; }

/// <summary>
/// Realm that will be used to connect to the database using the <see cref="IGraphClient"/>.
/// </summary>
public string? Realm { get; set; }

/// <summary>
/// Sets the encryption level for connecting to the database
/// </summary>
public EncryptionLevel? EncryptionLevel { get; set; }

/// <summary>
/// Sets the encryption level for connecting to the database
/// </summary>
public bool SerializeNullValues { get; set; } = false;

/// <summary>
/// Sets the encryption level for connecting to the database
/// </summary>
public bool UseDriverDataTypes { get; set; } = false;

/// <summary>
/// Creates instance of <see cref="Neo4jClientHealthCheckOptions"/>.
/// </summary>
/// <param name="graphClient">The client for connecting to the database.</param>
public Neo4jClientHealthCheckOptions(IGraphClient graphClient)
{
GraphClient = Guard.ThrowIfNull(graphClient);
}

/// <summary>
/// Creates instance of <see cref="Neo4jClientHealthCheckOptions"/>.
/// </summary>
/// <param name="host">Host that will be used to connect to the database. example: bolt://localhost:7687</param>
/// <param name="username">Username that will be used to connect to the database.</param>
/// <param name="password">Password that will be used to connect to the database.</param>
/// <param name="realm">realm that will be used to connect to the database.</param>
public Neo4jClientHealthCheckOptions(string? host, string? username, string? password, string? realm)
{
Host = Guard.ThrowIfNull(host, true);
Username = Guard.ThrowIfNull(username, true);
Password = Guard.ThrowIfNull(password, true);
Realm = realm;
}
}
24 changes: 24 additions & 0 deletions src/HealthChecks.Neo4jClient/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Neo4j Health check

This health сheck verifies the status of a database in a neo4j DBMS using the BOLT protocol to establish a connection

This library uses the [Neo4jClient](https://www.nuget.org/packages/Neo4jClient/) package to connect to the database

# Sample

Using options class
```csharp
var options = new Neo4jClientHealthCheckOptions("bolt://localhost:7687", "neo4j", "neo4j", realm: null);

services.AddHealthChecks()
.AddNeo4jClient(options);
```

Using client from service provider
```csharp
var graphClient = new BoltGraphClient("bolt://localhost:7687", "neo4j", "neo4j");
services.AddSingleton<IGraphClient>(graphClient);

services.AddHealthChecks()
.AddNeo4jClient(sp => sp.GetRequiredService<IGraphClient>());
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using Neo4jClient;

namespace HealthChecks.Neo4jClient.Tests.DependencyInjection;

public class RegistrationTests
{
[Fact]
public async Task add_health_check_when_properly_configured()
{
var services = new ServiceCollection();
var boltClient = new BoltGraphClient("bolt://localhost:7687", "neo4j", "neo4j");
await boltClient.ConnectAsync();
await boltClient.Cypher
.Create("(a:Test{Name: $param})")
.WithParam("param", "name123")
.ExecuteWithoutResultsAsync();

services.AddSingleton(boltClient);

services.AddHealthChecks()
.AddNeo4jClient(f => f.GetRequiredService<IGraphClient>());

await using var serviceProvider = services.BuildServiceProvider();
var options = serviceProvider.GetRequiredService<IOptions<HealthCheckServiceOptions>>();

var registration = options.Value.Registrations.First();

registration.Name.ShouldBe("neo4j");
}

[Fact]
public async Task add_health_check_when_an_instance_of_bolt_graph_client_is_passed_to_options_class()
{
var services = new ServiceCollection();
var boltClient = new BoltGraphClient("bolt://localhost:7687", "neo4j", "neo4j");
await boltClient.ConnectAsync();
await boltClient.Cypher
.Create("(a:Test{Name: $param})")
.WithParam("param", "name123")
.ExecuteWithoutResultsAsync();

services.AddSingleton(boltClient);

var healthCheckOptions = new Neo4jClientHealthCheckOptions(boltClient);

services.AddHealthChecks()
.AddNeo4jClient(healthCheckOptions);

await using var serviceProvider = services.BuildServiceProvider();
var options = serviceProvider.GetRequiredService<IOptions<HealthCheckServiceOptions>>();

var registration = options.Value.Registrations.First();

registration.Name.ShouldBe("neo4j");
}

[Fact]
public async Task add_health_check_when_bolt_graph_client_configured_from_options_class()
{
var services = new ServiceCollection();
var healthCheckOptions = new Neo4jClientHealthCheckOptions("bolt://localhost:7687", "neo4j", "neo4j", null);

services.AddHealthChecks()
.AddNeo4jClient(healthCheckOptions);

await using var serviceProvider = services.BuildServiceProvider();
var options = serviceProvider.GetRequiredService<IOptions<HealthCheckServiceOptions>>();

var registration = options.Value.Registrations.First();

registration.Name.ShouldBe("neo4j");
}
}
Loading