1+ using System ;
2+ using System . Linq ;
3+ using System . Threading . Tasks ;
4+ using Cleipnir . ResilientFunctions . CoreRuntime . Watchdogs ;
5+ using Cleipnir . ResilientFunctions . Domain ;
6+ using Cleipnir . ResilientFunctions . Helpers ;
7+ using Cleipnir . ResilientFunctions . Storage ;
8+ using Cleipnir . ResilientFunctions . Tests . Utils ;
9+ using Shouldly ;
10+
11+ namespace Cleipnir . ResilientFunctions . Tests . TestTemplates . WatchDogsTests ;
12+
13+ public abstract class ReplicaWatchdogTests
14+ {
15+ public abstract Task SunshineScenario ( ) ;
16+ public async Task SunshineScenario ( Task < IFunctionStore > storeTask )
17+ {
18+ var store = await storeTask . SelectAsync ( s => s . ReplicaStore ) ;
19+ var replicaId1 = new ReplicaId ( Guid . Parse ( "10000000-0000-0000-0000-000000000000" ) ) ;
20+ using var watchdog1 = new ReplicaWatchdog (
21+ replicaId1 ,
22+ store ,
23+ checkFrequency : TimeSpan . FromHours ( 1 ) ,
24+ onStrikeOut : _ => { }
25+ ) ;
26+ await watchdog1 . Initialize ( ) ;
27+ var allReplicas = await store . GetAll ( ) ;
28+ allReplicas . Count . ShouldBe ( 1 ) ;
29+ var storedReplica1 = allReplicas . Single ( sr => sr . ReplicaId == replicaId1 . Id ) ;
30+ storedReplica1 . Heartbeat . ShouldBe ( 0 ) ;
31+
32+ var replicaId2 = new ReplicaId ( Guid . Parse ( "20000000-0000-0000-0000-000000000000" ) ) ;
33+ using var watchdog2 = new ReplicaWatchdog (
34+ replicaId2 ,
35+ store ,
36+ checkFrequency : TimeSpan . FromHours ( 1 ) ,
37+ onStrikeOut : _ => { }
38+ ) ;
39+ await watchdog2 . Initialize ( ) ;
40+ allReplicas = await store . GetAll ( ) ;
41+ allReplicas . Count . ShouldBe ( 2 ) ;
42+ storedReplica1 = allReplicas . Single ( sr => sr . ReplicaId == replicaId1 . Id ) ;
43+ storedReplica1 . Heartbeat . ShouldBe ( 0 ) ;
44+ var storedReplica2 = allReplicas . Single ( sr => sr . ReplicaId == replicaId2 . Id ) ;
45+ storedReplica2 . Heartbeat . ShouldBe ( 0 ) ;
46+
47+ await watchdog1 . PerformIteration ( ) ;
48+ var replicas = await store . GetAll ( ) ;
49+ replicas . Single ( sr => sr . ReplicaId == replicaId1 . Id ) . Heartbeat . ShouldBe ( 1 ) ;
50+ replicas . Single ( sr => sr . ReplicaId == replicaId2 . Id ) . Heartbeat . ShouldBe ( 0 ) ;
51+ watchdog1 . Strikes [ new StoredReplica ( replicaId2 . Id , Heartbeat : 0 ) ] . ShouldBe ( 0 ) ;
52+ watchdog1 . Strikes [ new StoredReplica ( replicaId1 . Id , Heartbeat : 1 ) ] . ShouldBe ( 0 ) ;
53+
54+ await watchdog1 . PerformIteration ( ) ;
55+ replicas = await store . GetAll ( ) ;
56+ replicas . Single ( sr => sr . ReplicaId == replicaId1 . Id ) . Heartbeat . ShouldBe ( 2 ) ;
57+ replicas . Single ( sr => sr . ReplicaId == replicaId2 . Id ) . Heartbeat . ShouldBe ( 0 ) ;
58+ watchdog1 . Strikes [ new StoredReplica ( replicaId2 . Id , Heartbeat : 0 ) ] . ShouldBe ( 1 ) ;
59+ watchdog1 . Strikes [ new StoredReplica ( replicaId1 . Id , Heartbeat : 2 ) ] . ShouldBe ( 0 ) ;
60+
61+ await watchdog1 . PerformIteration ( ) ;
62+ replicas = await store . GetAll ( ) ;
63+ replicas . Count . ShouldBe ( 1 ) ;
64+ replicas . Single ( sr => sr . ReplicaId == replicaId1 . Id ) . Heartbeat . ShouldBe ( 3 ) ;
65+ watchdog1 . Strikes . Count . ShouldBe ( 1 ) ;
66+ watchdog1 . Strikes [ new StoredReplica ( replicaId1 . Id , Heartbeat : 3 ) ] . ShouldBe ( 0 ) ;
67+ }
68+
69+ public abstract Task ReplicaWatchdogStartResultsInAddedReplicaInStore ( ) ;
70+ public async Task ReplicaWatchdogStartResultsInAddedReplicaInStore ( Task < IFunctionStore > storeTask )
71+ {
72+ var store = await storeTask . SelectAsync ( s => s . ReplicaStore ) ;
73+ var replicaId1 = new ReplicaId ( Guid . Parse ( "10000000-0000-0000-0000-000000000000" ) ) ;
74+ using var watchdog1 = new ReplicaWatchdog (
75+ replicaId1 ,
76+ store ,
77+ checkFrequency : TimeSpan . FromHours ( 1 ) ,
78+ onStrikeOut : _ => { }
79+ ) ;
80+ await watchdog1 . Start ( ) ;
81+ var allReplicas = await store . GetAll ( ) ;
82+ allReplicas . Count . ShouldBe ( 1 ) ;
83+
84+ var replicaId2 = new ReplicaId ( Guid . Parse ( "20000000-0000-0000-0000-000000000000" ) ) ;
85+ using var watchdog2 = new ReplicaWatchdog (
86+ replicaId2 ,
87+ store ,
88+ checkFrequency : TimeSpan . FromHours ( 1 ) ,
89+ onStrikeOut : _ => { }
90+ ) ;
91+ await watchdog2 . Start ( ) ;
92+ allReplicas = await store . GetAll ( ) ;
93+ allReplicas . Count . ShouldBe ( 2 ) ;
94+ }
95+
96+ public abstract Task StrikedOutReplicaIsRemovedFromStore ( ) ;
97+ public async Task StrikedOutReplicaIsRemovedFromStore ( Task < IFunctionStore > storeTask )
98+ {
99+ var store = await storeTask . SelectAsync ( s => s . ReplicaStore ) ;
100+ var toBeStrikedOut = Guid . NewGuid ( ) ;
101+ Guid ? strikedOut = null ;
102+ await store . Insert ( toBeStrikedOut ) ;
103+ var replicaId1 = new ReplicaId ( Guid . Parse ( "10000000-0000-0000-0000-000000000000" ) ) ;
104+ using var watchdog1 = new ReplicaWatchdog (
105+ replicaId1 ,
106+ store ,
107+ checkFrequency : TimeSpan . FromHours ( 1 ) ,
108+ onStrikeOut : id => strikedOut = id
109+ ) ;
110+ await watchdog1 . Initialize ( ) ;
111+ await watchdog1 . PerformIteration ( ) ;
112+ strikedOut . ShouldBeNull ( ) ;
113+ await watchdog1 . PerformIteration ( ) ;
114+ strikedOut . ShouldBeNull ( ) ;
115+ await watchdog1 . PerformIteration ( ) ;
116+ strikedOut . ShouldBe ( toBeStrikedOut ) ;
117+
118+ var all = await store . GetAll ( ) ;
119+ all . Count . ShouldBe ( 1 ) ;
120+ all . Single ( ) . ReplicaId . ShouldBe ( replicaId1 . Id ) ;
121+ }
122+
123+ public abstract Task RunningWatchdogUpdatesItsOwnHeartbeat ( ) ;
124+ public async Task RunningWatchdogUpdatesItsOwnHeartbeat ( Task < IFunctionStore > storeTask )
125+ {
126+ var store = await storeTask . SelectAsync ( s => s . ReplicaStore ) ;
127+ var anyStrikesOut = false ;
128+ var replicaId1 = new ReplicaId ( Guid . NewGuid ( ) ) ;
129+ using var watchdog1 = new ReplicaWatchdog (
130+ replicaId1 ,
131+ store ,
132+ checkFrequency : TimeSpan . FromMilliseconds ( 100 ) ,
133+ onStrikeOut : _ => anyStrikesOut = true
134+ ) ;
135+
136+ await watchdog1 . Start ( ) ;
137+
138+ await BusyWait . Until ( async ( ) =>
139+ {
140+ var all = await store . GetAll ( ) ;
141+ all . Count . ShouldBe ( 1 ) ;
142+ var single = all . Single ( ) ;
143+ single . ReplicaId . ShouldBe ( replicaId1 . Id ) ;
144+ return single . Heartbeat > 0 ;
145+ } ) ;
146+
147+ anyStrikesOut . ShouldBe ( false ) ;
148+ }
149+
150+ public abstract Task ReplicaIdOffsetIfCalculatedCorrectly ( ) ;
151+ public async Task ReplicaIdOffsetIfCalculatedCorrectly ( Task < IFunctionStore > storeTask )
152+ {
153+ var store = await storeTask . SelectAsync ( s => s . ReplicaStore ) ;
154+
155+ var replicaId1 = new ReplicaId ( Guid . Parse ( "10000000-0000-0000-0000-000000000000" ) ) ;
156+ var replicaId2 = new ReplicaId ( Guid . Parse ( "20000000-0000-0000-0000-000000000000" ) ) ;
157+ var replicaId3 = new ReplicaId ( Guid . Parse ( "30000000-0000-0000-0000-000000000000" ) ) ;
158+
159+ var watchdog1 = new ReplicaWatchdog ( replicaId1 , store , checkFrequency : TimeSpan . FromHours ( 1 ) , onStrikeOut : _ => { } ) ;
160+ var watchdog2 = new ReplicaWatchdog ( replicaId2 , store , checkFrequency : TimeSpan . FromHours ( 1 ) , onStrikeOut : _ => { } ) ;
161+ var watchdog3 = new ReplicaWatchdog ( replicaId3 , store , checkFrequency : TimeSpan . FromHours ( 1 ) , onStrikeOut : _ => { } ) ;
162+
163+ await watchdog1 . Initialize ( ) ;
164+ await watchdog2 . Initialize ( ) ;
165+ await watchdog3 . Initialize ( ) ;
166+
167+ await watchdog3 . PerformIteration ( ) ;
168+ replicaId3 . Offset . ShouldBe ( 2 ) ;
169+ await watchdog2 . PerformIteration ( ) ;
170+ replicaId2 . Offset . ShouldBe ( 1 ) ;
171+ await watchdog1 . PerformIteration ( ) ;
172+ replicaId1 . Offset . ShouldBe ( 0 ) ;
173+ }
174+
175+ public abstract Task NonExistingReplicaIdOffsetIsNull ( ) ;
176+ public Task NonExistingReplicaIdOffsetIsNull ( Task < IFunctionStore > storeTask )
177+ {
178+ var offset = ReplicaWatchdog . CalculateOffset ( allReplicaIds : [ ] , ownReplicaId : Guid . NewGuid ( ) ) ;
179+ offset . ShouldBeNull ( ) ;
180+
181+ return Task . CompletedTask ;
182+ }
183+ }
0 commit comments