Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ A basic lambda function can handle it - the only reason this function is as comp
<TestSdkVersion>17.11.1</TestSdkVersion>
<CoverletVersion>6.0.3</CoverletVersion>
<XunitRunneVisualstudio>3.1.4</XunitRunneVisualstudio>
<AkkaVersion>1.5.50</AkkaVersion>
<AkkaVersion>1.5.51-alpha-1759266431</AkkaVersion>
<MicrosoftExtensionsVersion>[6.0.0,)</MicrosoftExtensionsVersion>
<SystemTextJsonVersion>[6.0.10,)</SystemTextJsonVersion>
</PropertyGroup>
Expand Down
5 changes: 5 additions & 0 deletions nuget.config
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@
<!--To inherit the global NuGet package sources remove the <clear/> line below -->
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<add key="testlab" value="https://nuget.testlab.petabridge.net/v3/index.json"/>
</packageSources>
<packageSourceMapping>
<packageSource key="testlab">
<package pattern="Akka"/>
<package pattern="Akka.*"/>
</packageSource>
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
Expand Down
31 changes: 31 additions & 0 deletions src/Akka.Persistence.Hosting/AkkaPersistenceHostingExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Akka.Persistence.Journal;
using Akka.Util;
using Akka.Actor;
using Microsoft.Extensions.Diagnostics.HealthChecks;

#nullable enable
namespace Akka.Persistence.Hosting
Expand Down Expand Up @@ -37,13 +38,29 @@ public sealed class AkkaPersistenceJournalBuilder
internal readonly AkkaConfigurationBuilder Builder;
internal readonly Dictionary<Type, HashSet<string>> Bindings = new Dictionary<Type, HashSet<string>>();
internal readonly Dictionary<string, Type> Adapters = new Dictionary<string, Type>();
internal AkkaHealthCheckRegistration? HealthCheckRegistration = null;

public AkkaPersistenceJournalBuilder(string journalId, AkkaConfigurationBuilder builder)
{
JournalId = journalId;
Builder = builder;
}

/// <summary>
/// Uses the built-in journal health check on the Akka.Persistence.Journal.
/// </summary>
/// <param name="unHealthyStatus">Default status to return when the plugin reports <see cref="PersistenceHealthStatus.Unhealthy"/>
/// or <see cref="PersistenceHealthStatus.Degraded"/>. Defaults to degraded.</param>
/// <param name="name">Optional name to add to the health check.</param>
/// <returns></returns>
public AkkaPersistenceJournalBuilder WithHealthCheck(HealthStatus unHealthyStatus = HealthStatus.Degraded,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registers the Akka.Persistence health check definition is inside the AsyncWriteJournal - by default it just looks as the state of the CircuitBreaker, which is a "good enough" heuristic typically.

string? name = null)
{
var registration = AddHealthCheck(name, unHealthyStatus);
HealthCheckRegistration = registration;
return this;
}

public AkkaPersistenceJournalBuilder AddEventAdapter<TAdapter>(string eventAdapterName,
IEnumerable<Type> boundTypes) where TAdapter : IEventAdapter
{
Expand Down Expand Up @@ -79,6 +96,16 @@ private void AddAdapter<TAdapter>(string eventAdapterName, IEnumerable<Type> bou
}
}

private AkkaHealthCheckRegistration AddHealthCheck(string? name, HealthStatus unHealthyStatus)
{
var registration = new AkkaHealthCheckRegistration(
name ?? $"Akka.Persistence.Journal.{JournalId}",
new JournalHealthCheck(JournalId),
unHealthyStatus,
["akka", "persistence", "journal"]);
return registration;
}

/// <summary>
/// INTERNAL API - Builds the HOCON and then injects it.
/// </summary>
Expand All @@ -98,6 +125,10 @@ internal void Build()
var finalHocon = ConfigurationFactory.ParseString(adapters.ToString())
.WithFallback(Persistence.DefaultConfig()); // add the default config as a fallback
Builder.AddHocon(finalHocon, HoconAddMode.Prepend);

// add the health checks if specified
if(HealthCheckRegistration != null)
Builder.WithHealthCheck(HealthCheckRegistration);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add the health checks to the AkkaConfigurationBuilder if they've been defined.

}

internal void AppendAdapters(StringBuilder sb)
Expand Down
64 changes: 64 additions & 0 deletions src/Akka.Persistence.Hosting/HealthChecks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using System.Threading;
using System.Threading.Tasks;
using Akka.Hosting;
using Microsoft.Extensions.Diagnostics.HealthChecks;

namespace Akka.Persistence.Hosting;

internal static class HealthCheckExt

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maps the Akka.Persistence health check data structures into Microsoft.Extensions.HealthChecks data structures

{
public static HealthCheckResult ToHealthCheckResult(this PersistenceHealthCheckResult persistenceHealthStatus)
=> new(persistenceHealthStatus.Status.ToHealthStatus(), persistenceHealthStatus.Description,
persistenceHealthStatus.Exception, persistenceHealthStatus.Data);

public static HealthStatus ToHealthStatus(this PersistenceHealthStatus persistenceHealthStatus) => persistenceHealthStatus switch
{
PersistenceHealthStatus.Healthy => HealthStatus.Healthy,
PersistenceHealthStatus.Degraded => HealthStatus.Degraded,
_ => HealthStatus.Unhealthy
};
}

/// <summary>
/// INTERNAL API
///
/// Leverages internal Akka.Persistence APIs to perform a health check on a journal.
/// </summary>
internal sealed class JournalHealthCheck : IAkkaHealthCheck
{
private readonly string _journalPluginId;

public JournalHealthCheck(string journalPluginId)
{
_journalPluginId = journalPluginId;
}

public async Task<HealthCheckResult> CheckHealthAsync(AkkaHealthCheckContext context, CancellationToken cancellationToken = default)
{
var persistence = Persistence.Instance.Apply(context.ActorSystem);
var journalResult = await persistence.CheckJournalHealthAsync(_journalPluginId, cancellationToken);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Runs the health checks we added in akkadotnet/akka.net#7842 and pipes them through the MSFT.EXT.HealthCheck functionality

return journalResult.ToHealthCheckResult();
}
}

/// <summary>
/// INTERNAL API
///
/// Leverages internal Akka.Persistence APIs to perform a health check on the snapshot store.
/// </summary>
internal sealed class SnapshotStoreHealthCheck : IAkkaHealthCheck
{
private readonly string _snapshotStorePluginId;

public SnapshotStoreHealthCheck(string snapshotStorePluginId)
{
_snapshotStorePluginId = snapshotStorePluginId;
}

public async Task<HealthCheckResult> CheckHealthAsync(AkkaHealthCheckContext context, CancellationToken cancellationToken = default)
{
var persistence = Persistence.Instance.Apply(context.ActorSystem);
var ssResult = await persistence.CheckSnapshotStoreHealthAsync(_snapshotStorePluginId, cancellationToken);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the same as above, but for snapshots.

return ssResult.ToHealthCheckResult();
}
}
Loading