1+ package replicate .generationvoting ;
2+
3+ import org .junit .Test ;
4+ import replicate .common .ClusterTest ;
5+ import replicate .common .TestUtils ;
6+
7+ import java .io .IOException ;
8+ import java .util .Arrays ;
9+ import java .util .concurrent .CompletableFuture ;
10+
11+ import static org .junit .Assert .*;
12+
13+ /**
14+ * LeaderElectionProcessTest demonstrates the step-by-step leader election process
15+ * with generation numbers as described in the consensus introduction slides.
16+ *
17+ * This test corresponds to the "leader_election_process" diagram reference and shows:
18+ * 1. Athens chooses Generation Number 1 (higher than any seen)
19+ * 2. Sends prepare requests to majority (self + Byzantium)
20+ * 3. Nodes promise not to accept requests with lower generation numbers
21+ * 4. Athens becomes Leader for Generation Number 1
22+ *
23+ * The test validates the mathematical foundation of consensus algorithms:
24+ * - Generation numbers establish clear ordering and authority
25+ * - Majority quorums ensure consistency
26+ * - Promise mechanism prevents conflicting leadership
27+ */
28+ public class LeaderElectionProcessTest extends ClusterTest <GenerationVoting > {
29+
30+ @ Test
31+ public void demonstratesStepByStepLeaderElectionProcess () throws Exception {
32+ // Setup: Start a 3-node cluster (Athens, Byzantium, Cyrene)
33+ // This matches exactly the scenario from leader_election_process.puml
34+ super .nodes = TestUtils .startCluster (
35+ Arrays .asList ("athens" , "byzantium" , "cyrene" ),
36+ (name , config , clock , clientConnectionAddress , peerConnectionAddress , peerAddresses )
37+ -> new GenerationVoting (name , config , clock , clientConnectionAddress , peerConnectionAddress , peerAddresses )
38+ );
39+
40+ GenerationVoting athens = nodes .get ("athens" );
41+ GenerationVoting byzantium = nodes .get ("byzantium" );
42+ GenerationVoting cyrene = nodes .get ("cyrene" );
43+
44+ System .out .println ("=== LEADER ELECTION PROCESS TEST ===" );
45+ System .out .println ("Scenario: Athens wants to become leader and chooses Generation Number 1" );
46+
47+ // Step 1: Verify initial state - no promises made yet
48+ assertEquals ("Athens should start with generation 0" , 0 , athens .generation );
49+ assertEquals ("Byzantium should start with generation 0" , 0 , byzantium .generation );
50+ assertEquals ("Cyrene should start with generation 0" , 0 , cyrene .generation );
51+
52+ System .out .println ("✓ Initial state: Athens(0), Byzantium(0), Cyrene(0)" );
53+
54+ // Step 2: Athens wants to become leader (matches diagram exactly)
55+ // This exactly matches the diagram note: "Wants to become leader, Chooses Generation Number 1 (higher than any seen)"
56+ System .out .println ("\n --- PHASE 1: PREPARE (Generation Number Selection) ---" );
57+ System .out .println ("Athens wants to become leader" );
58+ System .out .println ("Athens chooses Generation Number 1 (higher than any seen)" );
59+
60+ // Step 3: Athens initiates leader election directly
61+ // This is much more natural and matches the diagram intention
62+ System .out .println (" Athens -> Byzantium: Prepare(generation=1)" );
63+ System .out .println (" Athens -> Cyrene: Prepare(generation=1)" );
64+
65+ // Athens runs election - will propose generation number higher than current (0)
66+ CompletableFuture <Integer > leaderElectionResult = athens .runElection ();
67+ Integer electedGeneration = leaderElectionResult .get (); // Wait for completion
68+
69+ // Step 4: Verify Promise responses
70+ // This matches: "Byzantium -> Athens: Promise(generation=1)" and "Cyrene -> Athens: Promise(generation=1)"
71+ System .out .println ("\n --- PHASE 2: PROMISE (Majority Agreement) ---" );
72+ System .out .println (" Byzantium checks: 1 > any promised? Yes! Promises not to accept requests with generation < 1" );
73+ System .out .println (" Cyrene checks: 1 > any promised? Yes! Promises not to accept requests with generation < 1" );
74+ System .out .println (" Byzantium -> Athens: Promise(generation=1)" );
75+ System .out .println (" Cyrene -> Athens: Promise(generation=1)" );
76+
77+ // Step 5: Athens becomes leader
78+ // This matches: "Got majority promises! Now Leader for Generation 1 Can proceed with Phase 2"
79+ assertEquals ("Athens should be elected with generation 1" , 1 , electedGeneration .intValue ());
80+ assertEquals ("Athens should have generation 1" , 1 , athens .generation );
81+ assertEquals ("Byzantium should have promised generation 1" , 1 , byzantium .generation );
82+ assertEquals ("Cyrene should have promised generation 1" , 1 , cyrene .generation );
83+
84+ System .out .println ("\n --- LEADERSHIP ESTABLISHED ---" );
85+ System .out .println ("Athens got majority promises!" );
86+ System .out .println ("Athens is now Leader for Generation Number 1" );
87+ System .out .println ("Athens can proceed with Phase 2 (Accept/Commit)" );
88+
89+ // Final state verification matching the diagram outcome
90+ System .out .println ("\n --- FINAL STATE VERIFICATION ---" );
91+ System .out .printf ("Athens (Leader): generation=%d%n" , athens .generation );
92+ System .out .printf ("Byzantium (Acceptor): generation=%d%n" , byzantium .generation );
93+ System .out .printf ("Cyrene (Acceptor): generation=%d%n" , cyrene .generation );
94+
95+ System .out .println ("\n ✓ Leader election process completed successfully!" );
96+ System .out .println ("\n Key Insights Demonstrated (matching leader_election_process.puml):" );
97+ System .out .println ("1. Candidate chooses generation number higher than any seen" );
98+ System .out .println ("2. Prepare requests sent to all acceptors" );
99+ System .out .println ("3. Acceptors promise not to accept lower generation numbers" );
100+ System .out .println ("4. Majority promises establish leadership authority" );
101+ System .out .println ("5. Leader can now proceed with consensus (Phase 2)" );
102+ }
103+
104+ @ Test
105+ public void demonstratesLeaderElectionWithNetworkPartition () throws Exception {
106+ // This test shows what happens when a node tries to become leader
107+ // but cannot reach the full majority due to network issues
108+ super .nodes = TestUtils .startCluster (
109+ Arrays .asList ("athens" , "byzantium" , "cyrene" ),
110+ (name , config , clock , clientConnectionAddress , peerConnectionAddress , peerAddresses )
111+ -> new GenerationVoting (name , config , clock , clientConnectionAddress , peerConnectionAddress , peerAddresses )
112+ );
113+
114+ GenerationVoting athens = nodes .get ("athens" );
115+ GenerationVoting byzantium = nodes .get ("byzantium" );
116+ GenerationVoting cyrene = nodes .get ("cyrene" );
117+
118+ System .out .println ("\n === LEADER ELECTION WITH NETWORK PARTITION TEST ===" );
119+
120+ // Step 1: Create network partition - Athens cannot reach Cyrene
121+ System .out .println ("Creating network partition: Athens cannot communicate with Cyrene" );
122+ athens .dropMessagesTo (cyrene );
123+ cyrene .dropMessagesTo (athens );
124+
125+ // Step 2: Athens attempts leader election but can only reach Byzantium
126+ // This should still succeed since Athens + Byzantium = majority (2 out of 3)
127+ System .out .println ("Athens attempts leader election with partial connectivity" );
128+ System .out .println ("Athens can only reach Byzantium (majority = 2 out of 3)" );
129+
130+ CompletableFuture <Integer > leaderElectionResult = athens .runElection ();
131+ Integer electedGeneration = leaderElectionResult .get ();
132+
133+ // Step 3: Verify partial quorum success
134+ assertEquals ("Athens should succeed with generation 1" , 1 , electedGeneration .intValue ());
135+ assertEquals ("Athens should have generation 1" , 1 , athens .generation );
136+ assertEquals ("Byzantium should have generation 1" , 1 , byzantium .generation );
137+ assertEquals ("Cyrene should remain at generation 0 (partitioned)" , 0 , cyrene .generation );
138+
139+ System .out .println ("✓ Leader election succeeded with partial quorum" );
140+ System .out .printf ("Final state: Athens(1), Byzantium(1), Cyrene(0)%n" );
141+
142+ System .out .println ("\n Key Insight: Majority quorum allows progress despite failures" );
143+ }
144+
145+ @ Test
146+ public void demonstratesCompetingLeaderElection () throws Exception {
147+ // This test shows what happens when multiple nodes try to become leader
148+ // simultaneously, demonstrating how generation numbers resolve conflicts
149+ super .nodes = TestUtils .startCluster (
150+ Arrays .asList ("athens" , "byzantium" , "cyrene" ),
151+ (name , config , clock , clientConnectionAddress , peerConnectionAddress , peerAddresses )
152+ -> new GenerationVoting (name , config , clock , clientConnectionAddress , peerConnectionAddress , peerAddresses )
153+ );
154+
155+ GenerationVoting athens = nodes .get ("athens" );
156+ GenerationVoting byzantium = nodes .get ("byzantium" );
157+ GenerationVoting cyrene = nodes .get ("cyrene" );
158+
159+ System .out .println ("\n === COMPETING LEADER ELECTION TEST ===" );
160+
161+ // Step 1: Athens becomes leader first
162+ System .out .println ("Athens initiates first leader election" );
163+ CompletableFuture <Integer > athensResult = athens .runElection ();
164+ Integer athensGeneration = athensResult .get ();
165+
166+ assertEquals ("Athens should get generation 1" , 1 , athensGeneration .intValue ());
167+ System .out .println ("✓ Athens elected as leader with generation 1" );
168+
169+ // Step 2: Byzantium attempts to become leader
170+ // It should get a higher generation number since it sees Athens' generation 1
171+ System .out .println ("Byzantium attempts to become leader" );
172+ CompletableFuture <Integer > byzantiumResult = byzantium .runElection ();
173+ Integer byzantiumGeneration = byzantiumResult .get ();
174+
175+ assertEquals ("Byzantium should get generation 2" , 2 , byzantiumGeneration .intValue ());
176+ System .out .println ("Byzantium elected as leader with generation 2" );
177+
178+ // Step 3: Athens tries again - should get even higher generation
179+ System .out .println ("Athens attempts to regain leadership" );
180+ CompletableFuture <Integer > athensSecondResult = athens .runElection ();
181+ Integer athensSecondGeneration = athensSecondResult .get ();
182+
183+ assertEquals ("Athens should get generation 3" , 3 , athensSecondGeneration .intValue ());
184+ System .out .println ("Athens regains leadership with generation 3" );
185+
186+ System .out .println ("\n Key Insight: Generation numbers prevent conflicting leadership" );
187+ System .out .println ("Higher generation numbers always take precedence" );
188+ }
189+ }
0 commit comments