Skip to content

Commit

Permalink
Add akka.hosting (#148)
Browse files Browse the repository at this point in the history
* Rename Akkka in example projectname to Akka

* Rename also namespace in current example

* Example implementation of using Akka.Healthchecks together with IHealthChecks

* Add Akka.Hosting

* Use common.props for Akka.Hosting version

* Add XML-DOCs

* Add GUID to persistence healthchecks

* Refactor API

* Code cleanup, remove accidental inclusion of local snapshot files

* Refactor service to probe

* Add custom IProbeProvider sample project

* Add documentation

* Revert changes to persistence probe, moved to its own PR.

Co-authored-by: Wessel Kranenborg <Wessel.Kranenborg@priva.com>
Co-authored-by: Wessel Kranenborg <kranenborg.fam@gmail.com>
Co-authored-by: Aaron Stannard <aaron@petabridge.com>
  • Loading branch information
4 people authored Dec 22, 2022
1 parent 7fc714f commit 4869fb6
Show file tree
Hide file tree
Showing 36 changed files with 1,411 additions and 10 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -304,3 +304,4 @@ __pycache__/
*.btm.cs
*.odx.cs
*.xsd.cs
src/Akka.HealthCheck.Hosting.Web.Example/snapshots/snapshot-Akka.HealthCheck-df1340d9ebac45c1a96f100d8d667fd0-7-638068266586153191
30 changes: 27 additions & 3 deletions Akka.HealthCheck.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26430.14
# Visual Studio Version 17
VisualStudioVersion = 17.3.32825.248
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{79D71264-186B-4F62-8930-35DD9ECCAF3B}"
ProjectSection(SolutionItems) = preProject
Expand All @@ -23,11 +23,19 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Akka.HealthCheck.Cluster",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Akka.HealthCheck.Cluster.Tests", "src\Akka.HealthCheck.Cluster.Tests\Akka.HealthCheck.Cluster.Tests.csproj", "{BA43AC18-0706-49D1-9B71-BA1EDC54ABC2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akkka.HealthCheck.Example", "src\Akkka.HealthCheck.Example\Akkka.HealthCheck.Example.csproj", "{6213BECB-4145-41BF-BAD0-C5D16412BBD5}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Akka.HealthCheck.Example", "src\Akka.HealthCheck.Example\Akka.HealthCheck.Example.csproj", "{6213BECB-4145-41BF-BAD0-C5D16412BBD5}"
ProjectSection(ProjectDependencies) = postProject
{642308A0-B6F7-4478-B286-3B8707A00F82} = {642308A0-B6F7-4478-B286-3B8707A00F82}
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akka.HealthCheck.Hosting.Web.Example", "src\Akka.HealthCheck.Hosting.Web.Example\Akka.HealthCheck.Hosting.Web.Example.csproj", "{437E9BF1-75AE-4D1E-A6AF-37F799FAF8A3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akka.HealthCheck.Hosting", "src\Akka.HealthCheck.Hosting\Akka.HealthCheck.Hosting.csproj", "{E39E17B7-4412-436B-B233-92150080B28B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akka.HealthCheck.Hosting.Web", "src\Akka.HealthCheck.Hosting.Web\Akka.HealthCheck.Hosting.Web.csproj", "{9C69B5DB-FCAE-4528-A6CE-2C36DB2B806D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akka.HealthCheck.Hosting.Web.Custom.Example", "src\Akka.HealthCheck.Hosting.Web.Custom.Example\Akka.HealthCheck.Hosting.Web.Custom.Example.csproj", "{1E2AD614-CF7D-4944-BB71-B9B829088157}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -62,6 +70,22 @@ Global
{6213BECB-4145-41BF-BAD0-C5D16412BBD5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6213BECB-4145-41BF-BAD0-C5D16412BBD5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6213BECB-4145-41BF-BAD0-C5D16412BBD5}.Release|Any CPU.Build.0 = Release|Any CPU
{437E9BF1-75AE-4D1E-A6AF-37F799FAF8A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{437E9BF1-75AE-4D1E-A6AF-37F799FAF8A3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{437E9BF1-75AE-4D1E-A6AF-37F799FAF8A3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{437E9BF1-75AE-4D1E-A6AF-37F799FAF8A3}.Release|Any CPU.Build.0 = Release|Any CPU
{E39E17B7-4412-436B-B233-92150080B28B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E39E17B7-4412-436B-B233-92150080B28B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E39E17B7-4412-436B-B233-92150080B28B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E39E17B7-4412-436B-B233-92150080B28B}.Release|Any CPU.Build.0 = Release|Any CPU
{9C69B5DB-FCAE-4528-A6CE-2C36DB2B806D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9C69B5DB-FCAE-4528-A6CE-2C36DB2B806D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9C69B5DB-FCAE-4528-A6CE-2C36DB2B806D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9C69B5DB-FCAE-4528-A6CE-2C36DB2B806D}.Release|Any CPU.Build.0 = Release|Any CPU
{1E2AD614-CF7D-4944-BB71-B9B829088157}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1E2AD614-CF7D-4944-BB71-B9B829088157}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1E2AD614-CF7D-4944-BB71-B9B829088157}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1E2AD614-CF7D-4944-BB71-B9B829088157}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
3 changes: 2 additions & 1 deletion Akka.HealthCheck.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@
Copyright (C) 2015 - $CURRENT_YEAR$ Petabridge, LLC &lt;https://petabridge.com&gt;&#xD;
&lt;/copyright&gt;&#xD;
-----------------------------------------------------------------------</s:String>
<s:String x:Key="/Default/Environment/Hierarchy/PsiConfigurationSettingsKey/CustomLocation/@EntryValue">C:\Users\aaron\AppData\Local\JetBrains\Transient\ReSharperPlatformVs15\v11_10617ea1\SolutionCaches</s:String></wpf:ResourceDictionary>
<s:String x:Key="/Default/Environment/Hierarchy/PsiConfigurationSettingsKey/CustomLocation/@EntryValue">C:\Users\aaron\AppData\Local\JetBrains\Transient\ReSharperPlatformVs15\v11_10617ea1\SolutionCaches</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Liveness/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
5 changes: 5 additions & 0 deletions build.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,9 @@ Target "RunTests" (fun _ ->
let projects =
match (isWindows) with
| true -> !! "./src/**/*.Tests.csproj"
-- "./src/**/*.Example.csproj"
| _ -> !! "./src/**/*.Tests.csproj" // if you need to filter specs for Linux vs. Windows, do it here
-- "./src/**/*.Example.csproj"

let runSingleProject project =
let arguments =
Expand All @@ -144,7 +146,9 @@ Target "RunTestsNet" (fun _ ->
let projects =
match (isWindows) with
| true -> !! "./src/**/*.Tests.csproj"
-- "./src/**/*.Example.csproj"
| _ -> !! "./src/**/*.Tests.csproj" // if you need to filter specs for Linux vs. Windows, do it here
-- "./src/**/*.Example.csproj"

let runSingleProject project =
let arguments =
Expand Down Expand Up @@ -243,6 +247,7 @@ Target "CreateNuget" (fun _ ->
let projects = !! "src/**/*.csproj"
-- "src/**/*Tests.csproj" // Don't publish unit tests
-- "src/**/*Tests*.csproj"
-- "src/**/*.Example.csproj" // Don't publish example projects

let runSingleProject project =
DotNetCli.Pack
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<PropertyGroup>
<TargetFrameworks>$(NetStandardLibVersion);$(NetLibVersion)</TargetFrameworks>
<Description>Akka.NET and Akka.Cluster healthchecks for environments like K8s, AWS, Azure, Pivotal Cloud Foundry, and more.</Description>
<LangVersion>9.0</LangVersion>
</PropertyGroup>


Expand Down
2 changes: 1 addition & 1 deletion src/Akka.HealthCheck.Cluster/ClusterLivenessProbe.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public sealed class ClusterLivenessProbe : ReceiveActor
public ClusterLivenessProbe() : this(DefaultClusterLivenessStatus)
{
}

public ClusterLivenessProbe(LivenessStatus livenessStatus)
{
_livenessStatus = livenessStatus;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
using Akka.HealthCheck;
using System;

namespace Akkka.HealthCheck.Example
namespace Akka.HealthCheck.Example
{
class Program
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Akka.HealthCheck.Hosting.Web\Akka.HealthCheck.Hosting.Web.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// -----------------------------------------------------------------------
// <copyright file="CustomHealthCheckReadinessProbe.cs" company="Petabridge, LLC">
// Copyright (C) 2015 - 2022 Petabridge, LLC <https://petabridge.com>
// </copyright>
// -----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Akka.Actor;
using Akka.Configuration;
using Akka.HealthCheck.Liveness;
using Akka.HealthCheck.Readiness;
using Microsoft.Extensions.Diagnostics.HealthChecks;

namespace Akka.HealthCheck.Hosting.Web.Custom.Example;

public class CustomHealthCheckReadinessProbe: IHealthCheck
{
private readonly IActorRef _probe;

public CustomHealthCheckReadinessProbe(ActorSystem system)
{
if (!AkkaHealthCheck.For(system).ReadinessProbes.TryGetValue("custom", out _probe!))
{
throw new ConfigurationException("Could not find readiness actor with key 'custom'.");
}
}

public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = new CancellationToken())
{
try
{
var status = await _probe.Ask<ReadinessStatus>(
message: GetCurrentReadiness.Instance,
cancellationToken: cancellationToken);

return status.IsReady
? HealthCheckResult.Healthy("healthy", new Dictionary<string, object> { ["message"] = status.StatusMessage })
: HealthCheckResult.Unhealthy("unhealthy", data: new Dictionary<string, object> { ["message"] = status.StatusMessage });
}
catch (Exception e)
{
return HealthCheckResult.Unhealthy("unhealthy", e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// -----------------------------------------------------------------------
// <copyright file="CustomProbe.cs" company="Petabridge, LLC">
// Copyright (C) 2015 - 2022 Petabridge, LLC <https://petabridge.com>
// </copyright>
// -----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using Akka.Actor;
using Akka.HealthCheck.Readiness;

namespace Akka.HealthCheck.Hosting.Web.Custom.Example;

public class CustomReadinessProbe: ReceiveActor, IWithTimers
{
private readonly string _timerKey = "periodic-timer";
private readonly string _timerSignal = "do-check";

private ReadinessStatus _readinessStatus;
private readonly HashSet<IActorRef> _subscribers = new ();

public CustomReadinessProbe() : this(new ReadinessStatus(false))
{
}

public CustomReadinessProbe(ReadinessStatus readinessStatus)
{
_readinessStatus = readinessStatus;

Receive<GetCurrentReadiness>(_ => {
Sender.Tell(_readinessStatus);
});

Receive<SubscribeToReadiness>(s =>
{
_subscribers.Add(s.Subscriber);
Context.Watch(s.Subscriber);
s.Subscriber.Tell(_readinessStatus);
});

Receive<UnsubscribeFromReadiness>(u =>
{
_subscribers.Remove(u.Subscriber);
Context.Unwatch(u.Subscriber);
});

Receive<Terminated>(t => {
_subscribers.Remove(t.ActorRef);
});

ReceiveAsync<string>(
s => s == "do-check",
async _ =>
{
// TODO: insert probe check here
_readinessStatus = new ReadinessStatus(true);
});
}

protected override void PreStart()
{
Timers.StartPeriodicTimer(_timerKey, _timerSignal, TimeSpan.FromSeconds(1));
}

public ITimerScheduler Timers { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// -----------------------------------------------------------------------
// <copyright file="CustomProbeProvider.cs" company="Petabridge, LLC">
// Copyright (C) 2015 - 2022 Petabridge, LLC <https://petabridge.com>
// </copyright>
// -----------------------------------------------------------------------

using Akka.Actor;

namespace Akka.HealthCheck.Hosting.Web.Custom.Example;

public sealed class CustomReadinessProbeProvider: ProbeProviderBase
{
public override Props ProbeProps => Props.Create(() => new CustomReadinessProbe());

public CustomReadinessProbeProvider(ActorSystem system) : base(system)
{
}
}
32 changes: 32 additions & 0 deletions src/Akka.HealthCheck.Hosting.Web.Custom.Example/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using Akka.HealthCheck.Hosting;
using Akka.HealthCheck.Hosting.Web.Custom.Example;
using Akka.Hosting;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;

var webBuilder = WebApplication.CreateBuilder(args);

webBuilder.Services
.AddHealthChecks()
.AddCheck<CustomHealthCheckReadinessProbe>("akka-custom-readiness", HealthStatus.Unhealthy, new [] { "akka", "ready", "custom" });

webBuilder.Services
.AddAkka("actor-system", (builder, serviceProvider) =>
{
builder
.WithHealthCheck(opt =>
{
opt.AddReadinessProvider<CustomReadinessProbeProvider>("custom");
});
});

var app = webBuilder.Build();

app.MapHealthChecks("/healthz/akka/ready/custom", new HealthCheckOptions
{
Predicate = healthCheck => healthCheck.Tags.IsSupersetOf(new [] { "akka", "ready", "custom" })
});

await app.RunAsync();
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Import Project="..\common.props" />

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Akka.Cluster.Hosting" Version="$(AkkaHostingVersion)" />
<PackageReference Include="Akka.Hosting" Version="$(AkkaHostingVersion)" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Akka.HealthCheck.Hosting.Web\Akka.HealthCheck.Hosting.Web.csproj" />
</ItemGroup>

<ItemGroup>
<Compile Remove="snapshots\**" />
<EmbeddedResource Remove="snapshots\**" />
<None Remove="snapshots\**" />
<Content Remove="snapshots\**" />
</ItemGroup>
</Project>
43 changes: 43 additions & 0 deletions src/Akka.HealthCheck.Hosting.Web.Example/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using Akka.Cluster.Hosting;
using Akka.Hosting;

namespace Akka.HealthCheck.Hosting.Web.Example;

public static class Program
{
public static async Task Main(string[] args)
{
var webBuilder = WebApplication.CreateBuilder(args);

webBuilder.Services
// Register all of the health check service with IServiceCollection
.WithAkkaHealthCheck(HealthCheckType.All)
.AddAkka("actor-system", (builder, serviceProvider) =>
{
builder
.AddHocon("akka.cluster.min-nr-of-members = 1", HoconAddMode.Prepend)
.WithClustering()
// Automatically detects which health checks were registered inside the health check middleware and starts them
.WithWebHealthCheck(serviceProvider)
.AddStartup((system, _) =>
{
var cluster = Akka.Cluster.Cluster.Get(system);
cluster.Join(cluster.SelfAddress);
});
});

var app = webBuilder.Build();

// Automatically detects which health checks were registered inside the health check middleware and maps their routes
app.MapAkkaHealthCheckRoutes(
prependPath:"/health",
optionConfigure: opt =>
{
// Use a custom response writer to output a json of all reported statuses
opt.ResponseWriter = Helper.JsonResponseWriter;
});

await app.RunAsync();
}

}
Loading

0 comments on commit 4869fb6

Please sign in to comment.