Skip to content

Commit

Permalink
Add Akka.Discovery.Config.Hosting (#1758)
Browse files Browse the repository at this point in the history
* Add Akka.Discovery.Config.Hosting

* Move Discovery.Config into Akka.Management project

---------

Co-authored-by: Aaron Stannard <aaron@petabridge.com>
  • Loading branch information
Arkatufus and Aaronontheweb committed May 19, 2023
1 parent 7f0452a commit 2bf82db
Show file tree
Hide file tree
Showing 9 changed files with 581 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Akka.Hosting.TestKit" Version="$(AkkaHostingVersion)" />
<PackageReference Include="Akka.Cluster.Hosting" Version="$(AkkaHostingVersion)" />
<PackageReference Include="Akka.Remote.Hosting" Version="$(AkkaHostingVersion)" />
<PackageReference Include="FluentAssertions" Version="$(FluentAssertionVersion)" />
Expand Down
82 changes: 82 additions & 0 deletions src/management/Akka.Management.Tests/AkkaManagementSettingsSpec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
using System.Linq;
using System.Net;
using Akka.Configuration;
using Akka.Hosting;
using Akka.Http.Dsl;
using Akka.Management.Dsl;
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
using static FluentAssertions.FluentActions;
using Route = System.ValueTuple<string, Akka.Http.Dsl.HttpModuleBase>;
Expand Down Expand Up @@ -42,6 +44,31 @@ public void SettingsDefaultValues()
http.RouteProviders[0].FullyQualifiedClassName.Should().Be("Akka.Management.Cluster.Bootstrap.ClusterBootstrapProvider, Akka.Management");
http.RouteProvidersReadOnly.Should().BeTrue();
}

[Fact(DisplayName = "AkkaManagementOptions should contain default values")]
public void OptionsDefaultValuesTest()
{
var builder = new AkkaConfigurationBuilder(new ServiceCollection(), "test");
builder.WithAkkaManagement(new AkkaManagementOptions());

var settings = AkkaManagementSettings.Create(builder.Configuration.Value);
var http = settings.Http;

var addresses = Dns.GetHostAddresses(Dns.GetHostName());
var defaultHostname = addresses
.First(ip => !Equals(ip, IPAddress.Any) && !Equals(ip, IPAddress.IPv6Any))
.ToString();

http.Hostname.Should().Be(defaultHostname);
http.Port.Should().Be(8558);
http.EffectiveBindHostname.Should().Be(defaultHostname);
http.EffectiveBindPort.Should().Be(8558);
http.BasePath.Should().BeEmpty();
http.RouteProviders.Count.Should().Be(1);
http.RouteProviders[0].Name.Should().Be("cluster-bootstrap");
http.RouteProviders[0].FullyQualifiedClassName.Should().Be("Akka.Management.Cluster.Bootstrap.ClusterBootstrapProvider, Akka.Management");
http.RouteProvidersReadOnly.Should().BeTrue();
}

[Fact(DisplayName = "AkkaManagementSetup should override AkkaManagementSettings value")]
public void SetupOverrideSettings()
Expand Down Expand Up @@ -72,6 +99,39 @@ public void SetupOverrideSettings()
http.RouteProvidersReadOnly.Should().BeFalse();
}

[Fact(DisplayName = "AkkaManagementOptions should override default values")]
public void OptionsOverrideConfigTest()
{
var options = new AkkaManagementOptions
{
HostName = "a",
Port = 1234,
BindHostName = "b",
BindPort = 1235,
BasePath = "c",
RouteProvidersReadOnly = false
};
options.WithRouteProvider<FakeRouteProvider>("test");

var builder = new AkkaConfigurationBuilder(new ServiceCollection(), "test");
builder.WithAkkaManagement(options);

var settings = AkkaManagementSettings.Create(builder.Configuration.Value);
var http = settings.Http;

http.Hostname.Should().Be("a");
http.Port.Should().Be(1234);
http.EffectiveBindHostname.Should().Be("b");
http.EffectiveBindPort.Should().Be(1235);
http.BasePath.Should().Be("c");
http.RouteProviders.Count.Should().Be(2);
http.RouteProviders[0].Should()
.BeEquivalentTo(new NamedRouteProvider("cluster-bootstrap", "Akka.Management.Cluster.Bootstrap.ClusterBootstrapProvider, Akka.Management"));
http.RouteProviders[1].Should()
.BeEquivalentTo(new NamedRouteProvider("test", typeof(FakeRouteProvider).AssemblyQualifiedName));
http.RouteProvidersReadOnly.Should().BeFalse();
}

[Fact(DisplayName = "AkkaManagementSetup.Apply should throw on invalid route provider type")]
public void InvalidRouteProviderType()
{
Expand All @@ -93,6 +153,28 @@ public void InvalidRouteProviderType()
.WithMessage("*already added");
}

[Fact(DisplayName = "AkkaManagementOptions.Apply should throw on invalid route provider type")]
public void OptionsInvalidRouteProviderType()
{
var options = new AkkaManagementOptions
{
RouteProviders = new Dictionary<string, Type?>
{
["test"] = typeof(FakeRouteProvider),
["invalid-route"] = typeof(InvalidRouteProvider)
}
};
var builder = new AkkaConfigurationBuilder(new ServiceCollection(), "test");

Invoking(() => options.Apply(builder))
.Should().ThrowExactly<ConfigurationException>()
.WithMessage("*invalid-route*").WithMessage("*InvalidRouteProvider*");

Invoking(() => options.WithRouteProvider<FakeRouteProvider>("test2"))
.Should().ThrowExactly<ConfigurationException>()
.WithMessage("*already added");
}

private class InvalidRouteProvider
{
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// -----------------------------------------------------------------------
// <copyright file="ConfigServiceConfigSpec.cs" company="Akka.NET Project">
// Copyright (C) 2013-2023 .NET Foundation <https://github.com/akkadotnet/akka.net>
// </copyright>
// -----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using Akka.Discovery.Config;
using Akka.Discovery.Config.Hosting;
using Akka.Hosting;
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using Xunit;

namespace Akka.Management.Tests.Discovery.Config;

public class ConfigServiceConfigSpec
{
[Fact(DisplayName = "ConfigServiceDiscoveryOptions should generate proper HOCON config")]
public void OptionsShouldGenerateHoconConfig()
{
var builder = new AkkaConfigurationBuilder(new ServiceCollection(), "")
.WithConfigDiscovery(new ConfigServiceDiscoveryOptions
{
Services = new List<Service>
{
new Service
{
Name = "Test",
Endpoints = new[] { "abc:1", "def:2" }
}
}
});
var systemConfig = builder.Configuration.Value;
var config = systemConfig.GetConfig(ConfigServiceDiscoveryOptions.FullPath);

Type.GetType(config.GetString("class")).Should().Be(typeof(ConfigServiceDiscovery));
config.GetString("services-path").Should().Be("akka.discovery.config.services");
config.GetStringList("services.Test.endpoints").Should().BeEquivalentTo("abc:1", "def:2");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
// -----------------------------------------------------------------------
// <copyright file="ConfigServiceSpec.cs" company="Akka.NET Project">
// Copyright (C) 2009-2023 Lightbend Inc. <http://www.lightbend.com>
// Copyright (C) 2013-2023 .NET Foundation <https://github.com/akkadotnet/akka.net>
// </copyright>
// -----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Akka.Actor;
using Akka.Cluster.Hosting;
using Akka.Discovery.Config.Hosting;
using Akka.Hosting;
using Akka.Management.Cluster.Bootstrap;
using Akka.Remote.Hosting;
using Akka.Util;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Xunit;
using Xunit.Abstractions;

namespace Akka.Management.Tests.Discovery.Config.End2End;

public class ConfigServiceSpec: Akka.Hosting.TestKit.TestKit
{
private const int ClusterNodeCount = 3;

private readonly AtomicBoolean _clusterFormed = new ();
private readonly int[] _remotingPorts = new int[ClusterNodeCount];
private readonly int[] _managementPorts = new int[ClusterNodeCount];
private readonly string[] _managementEndpoints = new string[ClusterNodeCount];

private IHost? _host1;
private IHost? _host2;
private ActorSystem? _sys1;
private ActorSystem? _sys2;

public ConfigServiceSpec(ITestOutputHelper output) : base(nameof(ConfigServiceSpec), output)
{
var rnd = new Random();
var port = rnd.Next(30000, 40000);
for (var i = 0; i < ClusterNodeCount; i++)
{
_remotingPorts[i] = port;
port++;
_managementPorts[i] = port;
_managementEndpoints[i] = $"127.0.0.1:{port}";
port++;
}
}

#region Test Setup

private async Task<IHost> StartAkkaHost(int index)
{
var hostBuilder = new HostBuilder();
hostBuilder
.ConfigureLogging(logger =>
{
logger.ClearProviders();
logger.AddProvider(new XUnitLoggerProvider(Output!, LogLevel));
logger.AddFilter("Akka.*", LogLevel);
})
.ConfigureServices((_, services) =>
{
services.AddAkka(nameof(ConfigServiceSpec), (builder, _) =>
{
AddConfigDiscovery(builder, index);
});
});
var host = hostBuilder.Build();
await host.StartAsync();

return host;
}

private AkkaConfigurationBuilder AddConfigDiscovery(AkkaConfigurationBuilder builder, int index)
{
var port = _remotingPorts[index];
var managementPort = _managementPorts[index];

return builder
.WithRemoting(options =>
{
options.Port = port;
options.HostName = "localhost";
})
.WithClustering(new ClusterOptions
{
MinimumNumberOfMembers = 3
})
.WithClusterBootstrap(
requiredContactPoints:3,
serviceName:"LocalService",
// NOTE: this is needed to prevent cluster bootstrap from filtering out multiple result from a single domain name
portName:"port")
.WithAkkaManagement(
hostName:"127.0.0.1",
port: managementPort,
bindHostname:"127.0.0.1",
bindPort:managementPort,
autoStart:true)
.WithConfigDiscovery(opt =>
{
opt.Services.Add(new Service
{
Name = "LocalService",
Endpoints = _managementEndpoints
});
})
.WithConfigDiscovery(new ConfigServiceDiscoveryOptions
{
Services = new List<Service>
{
new Service
{
Name = "LocalService",
Endpoints = _managementEndpoints
}
}
});
}

protected override void ConfigureAkka(AkkaConfigurationBuilder builder, IServiceProvider provider)
{
AddConfigDiscovery(builder, 0)
.AddStartup((system, _) =>
{
var cluster = Akka.Cluster.Cluster.Get(system);
cluster.RegisterOnMemberUp(() =>
{
_clusterFormed.CompareAndSet(false, true);
});
});
}

protected override async Task BeforeTestStart()
{
await base.BeforeTestStart();
_host1 = await StartAkkaHost(1);
_sys1 = _host1.Services.GetRequiredService<ActorSystem>();
_host2 = await StartAkkaHost(2);
_sys2 = _host2.Services.GetRequiredService<ActorSystem>();
Output!.WriteLine("Systems started");
}

protected override async Task AfterAllAsync()
{
await base.AfterAllAsync();

var tasks = new List<Task>();
if (_sys1 is not null)
tasks.Add(_sys1.Terminate());
if(_sys2 is not null)
tasks.Add(_sys2.Terminate());
await Task.WhenAll(tasks);

_host1?.Dispose();
_host2?.Dispose();
}

#endregion


[Fact(DisplayName = "Cluster should form")]
public async Task ClusterFormingSpec()
{
await AwaitConditionAsync(() => Task.FromResult(_clusterFormed.Value), max:TimeSpan.FromSeconds(30));
}
}

0 comments on commit 2bf82db

Please sign in to comment.