-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Expand file tree
/
Copy pathClusterSingletonManagerDownedSpec.cs
More file actions
192 lines (162 loc) · 6.3 KB
/
Copy pathClusterSingletonManagerDownedSpec.cs
File metadata and controls
192 lines (162 loc) · 6.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
//-----------------------------------------------------------------------
// <copyright file="ClusterSingletonManagerDownedSpec.cs" company="Akka.NET Project">
// Copyright (C) 2009-2022 Lightbend Inc. <http://www.lightbend.com>
// Copyright (C) 2013-2025 .NET Foundation <https://github.com/akkadotnet/akka.net>
// </copyright>
//-----------------------------------------------------------------------
using System;
using System.Linq;
using System.Threading.Tasks;
using Akka.Actor;
using Akka.Cluster.TestKit;
using Akka.Cluster.Tools.Singleton;
using Akka.Configuration;
using Akka.MultiNode.TestAdapter;
using Akka.Remote.TestKit;
using Akka.Remote.Transport;
using FluentAssertions;
using FluentAssertions.Extensions;
namespace Akka.Cluster.Tools.Tests.MultiNode.Singleton;
public class ClusterSingletonManagerDownedSpecConfig : MultiNodeConfig
{
public RoleName First { get; }
public RoleName Second { get; }
public RoleName Third { get; }
public ClusterSingletonManagerDownedSpecConfig()
{
First = Role("first");
Second = Role("second");
Third = Role("third");
CommonConfig = ConfigurationFactory.ParseString(@"
akka.loglevel = INFO
akka.actor.provider = ""Akka.Cluster.ClusterActorRefProvider, Akka.Cluster""
akka.remote.log-remote-lifecycle-events = off
")
.WithFallback(ClusterSingleton.DefaultConfig())
.WithFallback(ClusterSingletonProxy.DefaultConfig())
.WithFallback(MultiNodeClusterSpec.ClusterConfig());
TestTransport = true;
}
internal class EchoStarted
{
public static readonly EchoStarted Instance = new();
private EchoStarted()
{
}
}
internal class EchoStopped
{
public static readonly EchoStopped Instance = new();
private EchoStopped()
{
}
}
/// <summary>
/// The singleton actor
/// </summary>
internal class Echo : UntypedActor
{
private readonly IActorRef _testActorRef;
public Echo(IActorRef testActorRef)
{
_testActorRef = testActorRef;
_testActorRef.Tell(EchoStarted.Instance);
}
protected override void PostStop()
{
_testActorRef.Tell(EchoStopped.Instance);
}
public static Props Props(IActorRef testActorRef)
=> Actor.Props.Create(() => new Echo(testActorRef));
protected override void OnReceive(object message)
{
Sender.Tell(message);
}
}
}
public class ClusterSingletonManagerDownedSpec : MultiNodeClusterSpec
{
private readonly ClusterSingletonManagerDownedSpecConfig _config;
private readonly Lazy<IActorRef> _echoProxy;
protected override int InitialParticipantsValueFactory => Roles.Count;
public ClusterSingletonManagerDownedSpec() : this(new ClusterSingletonManagerDownedSpecConfig())
{
}
protected ClusterSingletonManagerDownedSpec(ClusterSingletonManagerDownedSpecConfig config) : base(config, typeof(ClusterSingletonManagerDownedSpec))
{
_config = config;
_echoProxy = new Lazy<IActorRef>(() => Watch(Sys.ActorOf(ClusterSingletonProxy.Props(
singletonManagerPath: "/user/echo",
settings: ClusterSingletonProxySettings.Create(Sys)),
name: "echoProxy")));
}
private async Task Join(RoleName from, RoleName to)
{
RunOn(() =>
{
Cluster.Join(Node(to).Address);
CreateSingleton();
}, from);
await EnterBarrierAsync(from.Name + "-joined");
}
private IActorRef CreateSingleton()
{
return Sys.ActorOf(ClusterSingletonManager.Props(
singletonProps: ClusterSingletonManagerDownedSpecConfig.Echo.Props(TestActor),
terminationMessage: PoisonPill.Instance,
settings: ClusterSingletonManagerSettings.Create(Sys)),
name: "echo");
}
[MultiNodeFact]
public async Task ClusterSingletonManagerDownedSpecs()
{
await ClusterSingletonManager_downing_must_startup_3_node();
await ClusterSingletonManager_downing_must_stop_instance_when_member_is_downed();
}
private async Task ClusterSingletonManager_downing_must_startup_3_node()
{
await Join(_config.First, _config.First);
await Join(_config.Second, _config.First);
await Join(_config.Third, _config.First);
await WithinAsync(15.Seconds(), async () =>
{
await AwaitAssertAsync(() =>
{
Cluster.State.Members.Count(m => m.Status == MemberStatus.Up).Should().Be(3);
return Task.CompletedTask;
});
});
RunOn(() =>
{
ExpectMsg(ClusterSingletonManagerDownedSpecConfig.EchoStarted.Instance);
}, _config.First);
await EnterBarrierAsync("started");
}
private async Task ClusterSingletonManager_downing_must_stop_instance_when_member_is_downed()
{
await RunOnAsync(async () =>
{
await TestConductor.BlackholeAsync(_config.First, _config.Third, ThrottleTransportAdapter.Direction.Both);
await TestConductor.BlackholeAsync(_config.Second, _config.Third, ThrottleTransportAdapter.Direction.Both);
await WithinAsync(15.Seconds(), async () =>
{
await AwaitAssertAsync(() =>
{
Cluster.State.Unreachable.Count.Should().Be(1);
return Task.CompletedTask;
});
});
}, _config.First);
await EnterBarrierAsync("blackhole-1");
await RunOnAsync(async () =>
{
// another blackhole so that second can't mark gossip as seen and thereby deferring shutdown of first
await TestConductor.BlackholeAsync(_config.First, _config.Second, ThrottleTransportAdapter.Direction.Both);
Cluster.Down((await NodeAsync(_config.Second)).Address);
Cluster.Down(Cluster.SelfAddress);
// singleton instance stopped, before failure detection of first-second
await ExpectMsgAsync<ClusterSingletonManagerDownedSpecConfig.EchoStopped>(TimeSpan.FromSeconds(3));
}, _config.First);
await EnterBarrierAsync("stopped");
}
}