Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Cluster singleton should consider Member AppVersion during hand over. (…
…#6065) * Cluster singleton should consider Member AppVersion during hand over. * Add `akka.cluster.singleton.consider-app-version` HOCON setting as opt-in flag * Update API Verify list * Add MemberAgeOrdering spec * Change insertion order * Update API Verify list Co-authored-by: Aaron Stannard <aaron@petabridge.com>
- Loading branch information
1 parent
d55f67f
commit 9acaf00
Showing
18 changed files
with
406 additions
and
47 deletions.
There are no files selected for viewing
135 changes: 135 additions & 0 deletions
135
src/contrib/cluster/Akka.Cluster.Tools.Tests/Singleton/ClusterSingletonRestart3Spec.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
//----------------------------------------------------------------------- | ||
// <copyright file="ClusterSingletonRestart2Spec.cs" company="Akka.NET Project"> | ||
// Copyright (C) 2009-2021 Lightbend Inc. <http://www.lightbend.com> | ||
// Copyright (C) 2013-2021 .NET Foundation <https://github.com/akkadotnet/akka.net> | ||
// </copyright> | ||
//----------------------------------------------------------------------- | ||
|
||
using System; | ||
using System.Collections.Immutable; | ||
using System.Linq; | ||
using System.Threading.Tasks; | ||
using Akka.Actor; | ||
using Akka.Cluster.Tools.Singleton; | ||
using Akka.Configuration; | ||
using Akka.TestKit; | ||
using FluentAssertions; | ||
using Xunit; | ||
using Xunit.Abstractions; | ||
|
||
namespace Akka.Cluster.Tools.Tests.Singleton | ||
{ | ||
public class ClusterSingletonRestart3Spec : AkkaSpec | ||
{ | ||
private readonly ActorSystem _sys1; | ||
private readonly ActorSystem _sys2; | ||
private readonly ActorSystem _sys3; | ||
|
||
public ClusterSingletonRestart3Spec(ITestOutputHelper output) : base(@" | ||
akka.loglevel = DEBUG | ||
akka.actor.provider = ""cluster"" | ||
akka.cluster.app-version = ""1.0.0"" | ||
akka.cluster.auto-down-unreachable-after = 2s | ||
akka.cluster.singleton.min-number-of-hand-over-retries = 5 | ||
akka.cluster.singleton.consider-app-version = true | ||
akka.remote { | ||
dot-netty.tcp { | ||
hostname = ""127.0.0.1"" | ||
port = 0 | ||
} | ||
}", output) | ||
{ | ||
_sys1 = Sys; | ||
_sys2 = ActorSystem.Create(Sys.Name, Sys.Settings.Config); | ||
InitializeLogger(_sys2); | ||
_sys3 = ActorSystem.Create(Sys.Name, ConfigurationFactory.ParseString("akka.cluster.app-version = \"1.0.2\"") | ||
.WithFallback(Sys.Settings.Config)); | ||
InitializeLogger(_sys3); | ||
} | ||
|
||
public void Join(ActorSystem from, ActorSystem to) | ||
{ | ||
from.ActorOf(ClusterSingletonManager.Props(Props.Create(() => new Singleton()), | ||
PoisonPill.Instance, | ||
ClusterSingletonManagerSettings.Create(from)), "echo"); | ||
|
||
|
||
Within(TimeSpan.FromSeconds(45), () => | ||
{ | ||
AwaitAssert(() => | ||
{ | ||
Cluster.Get(from).Join(Cluster.Get(to).SelfAddress); | ||
Cluster.Get(from).State.Members.Select(x => x.UniqueAddress).Should().Contain(Cluster.Get(from).SelfUniqueAddress); | ||
Cluster.Get(from) | ||
.State.Members.Select(x => x.Status) | ||
.ToImmutableHashSet() | ||
.Should() | ||
.Equal(ImmutableHashSet<MemberStatus>.Empty.Add(MemberStatus.Up)); | ||
}); | ||
}); | ||
} | ||
|
||
[Fact] | ||
public void Singleton_should_consider_AppVersion_when_handing_over() | ||
{ | ||
Join(_sys1, _sys1); | ||
Join(_sys2, _sys1); | ||
|
||
var proxy2 = _sys2.ActorOf( | ||
ClusterSingletonProxy.Props("user/echo", ClusterSingletonProxySettings.Create(_sys2)), "proxy2"); | ||
|
||
Within(TimeSpan.FromSeconds(5), () => | ||
{ | ||
AwaitAssert(() => | ||
{ | ||
var probe = CreateTestProbe(_sys2); | ||
proxy2.Tell("poke", probe.Ref); | ||
var singleton = probe.ExpectMsg<Member>(TimeSpan.FromSeconds(1)); | ||
singleton.Should().Be(Cluster.Get(_sys1).SelfMember); | ||
singleton.AppVersion.Version.Should().Be("1.0.0"); | ||
}); | ||
}); | ||
|
||
// A new node with higher AppVersion joins the cluster | ||
Join(_sys3, _sys1); | ||
|
||
// Old node with the singleton instance left the cluster | ||
Cluster.Get(_sys1).Leave(Cluster.Get(_sys1).SelfAddress); | ||
|
||
// let it stabilize | ||
Task.Delay(TimeSpan.FromSeconds(5)).Wait(); | ||
|
||
Within(TimeSpan.FromSeconds(10), () => | ||
{ | ||
AwaitAssert(() => | ||
{ | ||
var probe = CreateTestProbe(_sys2); | ||
proxy2.Tell("poke", probe.Ref); | ||
// note that _sys3 has a higher app-version, so the singleton should start there | ||
var singleton = probe.ExpectMsg<Member>(TimeSpan.FromSeconds(1)); | ||
singleton.Should().Be(Cluster.Get(_sys3).SelfMember); | ||
singleton.AppVersion.Version.Should().Be("1.0.2"); | ||
}); | ||
}); | ||
} | ||
|
||
protected override async Task AfterAllAsync() | ||
{ | ||
await base.AfterAllAsync(); | ||
await ShutdownAsync(_sys2); | ||
await ShutdownAsync(_sys3); | ||
} | ||
|
||
public class Singleton : ReceiveActor | ||
{ | ||
public Singleton() | ||
{ | ||
ReceiveAny(o => | ||
{ | ||
Sender.Tell(Cluster.Get(Context.System).SelfMember); | ||
}); | ||
} | ||
} | ||
} | ||
} |
89 changes: 89 additions & 0 deletions
89
src/contrib/cluster/Akka.Cluster.Tools.Tests/Singleton/MemberAgeOrderingSpec.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
// ----------------------------------------------------------------------- | ||
// <copyright file="MemberAgeOrderingSpec.cs" company="Akka.NET Project"> | ||
// Copyright (C) 2009-2022 Lightbend Inc. <http://www.lightbend.com> | ||
// Copyright (C) 2013-2022 .NET Foundation <https://github.com/akkadotnet/akka.net> | ||
// </copyright> | ||
// ----------------------------------------------------------------------- | ||
|
||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
using System.Linq; | ||
using Akka.Actor; | ||
using Akka.Cluster.Tools.Singleton; | ||
using Akka.Util; | ||
using FluentAssertions; | ||
using Xunit; | ||
|
||
namespace Akka.Cluster.Tools.Tests.Singleton | ||
{ | ||
public class MemberAgeOrderingSpec | ||
{ | ||
[Fact(DisplayName = "MemberAgeOrdering should sort based on UpNumber")] | ||
public void SortByUpNumberTest() | ||
{ | ||
var members = new SortedSet<Member>(MemberAgeOrdering.DescendingWithAppVersion) | ||
{ | ||
Create(Address.Parse("akka://sys@darkstar:1112"), upNumber: 3), | ||
Create(Address.Parse("akka://sys@darkstar:1113"), upNumber: 1), | ||
Create(Address.Parse("akka://sys@darkstar:1111"), upNumber: 9), | ||
}; | ||
|
||
var seq = members.ToList(); | ||
seq.Count.Should().Be(3); | ||
seq[0].Should().Be(Create(Address.Parse("akka://sys@darkstar:1113"), upNumber: 1)); | ||
seq[1].Should().Be(Create(Address.Parse("akka://sys@darkstar:1112"), upNumber: 3)); | ||
seq[2].Should().Be(Create(Address.Parse("akka://sys@darkstar:1111"), upNumber: 9)); | ||
} | ||
|
||
[Fact(DisplayName = "MemberAgeOrdering should sort based on Address if UpNumber is the same")] | ||
public void SortByAddressTest() | ||
{ | ||
var members = new SortedSet<Member>(MemberAgeOrdering.DescendingWithAppVersion) | ||
{ | ||
Create(Address.Parse("akka://sys@darkstar:1112"), upNumber: 1), | ||
Create(Address.Parse("akka://sys@darkstar:1113"), upNumber: 1), | ||
Create(Address.Parse("akka://sys@darkstar:1111"), upNumber: 1), | ||
}; | ||
|
||
var seq = members.ToList(); | ||
seq.Count.Should().Be(3); | ||
seq[0].Should().Be(Create(Address.Parse("akka://sys@darkstar:1111"), upNumber: 1)); | ||
seq[1].Should().Be(Create(Address.Parse("akka://sys@darkstar:1112"), upNumber: 1)); | ||
seq[2].Should().Be(Create(Address.Parse("akka://sys@darkstar:1113"), upNumber: 1)); | ||
} | ||
|
||
[Fact(DisplayName = "MemberAgeOrdering should prefer AppVersion over UpNumber")] | ||
public void SortByAppVersionTest() | ||
{ | ||
var members = new SortedSet<Member>(MemberAgeOrdering.DescendingWithAppVersion) | ||
{ | ||
Create(Address.Parse("akka://sys@darkstar:1112"), upNumber: 3, appVersion: AppVersion.Create("1.0.0")), | ||
Create(Address.Parse("akka://sys@darkstar:1113"), upNumber: 1, appVersion: AppVersion.Create("1.0.0")), | ||
Create(Address.Parse("akka://sys@darkstar:1111"), upNumber: 2, appVersion: AppVersion.Create("1.0.0")), | ||
Create(Address.Parse("akka://sys@darkstar:1114"), upNumber: 7, appVersion: AppVersion.Create("1.0.2")), | ||
Create(Address.Parse("akka://sys@darkstar:1115"), upNumber: 8, appVersion: AppVersion.Create("1.0.2")), | ||
Create(Address.Parse("akka://sys@darkstar:1116"), upNumber: 6, appVersion: AppVersion.Create("1.0.2")), | ||
}; | ||
|
||
var seq = members.ToList(); | ||
seq.Count.Should().Be(6); | ||
seq[0].Should().Be(Create(Address.Parse("akka://sys@darkstar:1116"), upNumber: 6, appVersion: AppVersion.Create("1.0.2"))); | ||
seq[1].Should().Be(Create(Address.Parse("akka://sys@darkstar:1114"), upNumber: 7, appVersion: AppVersion.Create("1.0.2"))); | ||
seq[2].Should().Be(Create(Address.Parse("akka://sys@darkstar:1115"), upNumber: 8, appVersion: AppVersion.Create("1.0.2"))); | ||
seq[3].Should().Be(Create(Address.Parse("akka://sys@darkstar:1113"), upNumber: 1, appVersion: AppVersion.Create("1.0.0"))); | ||
seq[4].Should().Be(Create(Address.Parse("akka://sys@darkstar:1111"), upNumber: 2, appVersion: AppVersion.Create("1.0.0"))); | ||
seq[5].Should().Be(Create(Address.Parse("akka://sys@darkstar:1112"), upNumber: 3, appVersion: AppVersion.Create("1.0.0"))); | ||
} | ||
|
||
public static Member Create( | ||
Address address, | ||
MemberStatus status = MemberStatus.Up, | ||
ImmutableHashSet<string> roles = null, | ||
int uid = 0, | ||
int upNumber = 0, | ||
AppVersion appVersion = null) | ||
{ | ||
return Member.Create(new UniqueAddress(address, uid), upNumber, status, roles ?? ImmutableHashSet<string>.Empty, appVersion ?? AppVersion.Zero); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.