@@ -42,8 +42,10 @@ groups() ->
4242 delete_policy ,
4343 export_definitions
4444 ]},
45- {cluster_operation_add , [], [add_node ]},
46- {cluster_operation_remove , [], [remove_node ]},
45+ {cluster_operation , [], [add_node_when_seed_node_is_leader ,
46+ add_node_when_seed_node_is_follower ,
47+ remove_node_when_seed_node_is_leader ,
48+ remove_node_when_seed_node_is_follower ]},
4749 {feature_flags , [], [enable_feature_flag ]}
4850 ].
4951
@@ -127,26 +129,44 @@ init_per_group(Group, Config0) when Group == client_operations;
127129 partition_5_node_cluster (Config1 ),
128130 Config1
129131 end ;
130- init_per_group (Group , Config0 ) ->
132+ init_per_group (_Group , Config0 ) ->
131133 Config = rabbit_ct_helpers :set_config (Config0 , [{rmq_nodes_count , 5 },
132- {rmq_nodename_suffix , Group },
133134 {rmq_nodes_clustered , false },
134135 {tcp_ports_base },
135136 {net_ticktime , 5 }]),
136137 Config1 = rabbit_ct_helpers :merge_app_env (
137- Config , {rabbit , [{forced_feature_flags_on_init , []}]}),
138- rabbit_ct_helpers :run_steps (Config1 ,
139- rabbit_ct_broker_helpers :setup_steps () ++
140- rabbit_ct_client_helpers :setup_steps ()).
138+ Config , {rabbit , [{forced_feature_flags_on_init , []},
139+ {khepri_leader_wait_retry_timeout , 30000 }]}),
140+ Config1 .
141141
142142end_per_group (_ , Config ) ->
143- rabbit_ct_helpers :run_steps (Config ,
144- rabbit_ct_client_helpers :teardown_steps () ++
145- rabbit_ct_broker_helpers :teardown_steps ()).
146-
143+ Config .
144+
145+ init_per_testcase (Testcase , Config )
146+ when Testcase =:= add_node_when_seed_node_is_leader orelse
147+ Testcase =:= add_node_when_seed_node_is_follower orelse
148+ Testcase =:= remove_node_when_seed_node_is_leader orelse
149+ Testcase =:= remove_node_when_seed_node_is_follower ->
150+ rabbit_ct_helpers :testcase_started (Config , Testcase ),
151+ Config1 = rabbit_ct_helpers :set_config (
152+ Config , [{rmq_nodename_suffix , Testcase }]),
153+ rabbit_ct_helpers :run_steps (
154+ Config1 ,
155+ rabbit_ct_broker_helpers :setup_steps () ++
156+ rabbit_ct_client_helpers :setup_steps ());
147157init_per_testcase (Testcase , Config ) ->
148158 rabbit_ct_helpers :testcase_started (Config , Testcase ).
149159
160+ end_per_testcase (Testcase , Config )
161+ when Testcase =:= add_node_when_seed_node_is_leader orelse
162+ Testcase =:= add_node_when_seed_node_is_follower orelse
163+ Testcase =:= remove_node_when_seed_node_is_leader orelse
164+ Testcase =:= remove_node_when_seed_node_is_follower ->
165+ rabbit_ct_helpers :run_steps (
166+ Config ,
167+ rabbit_ct_client_helpers :teardown_steps () ++
168+ rabbit_ct_broker_helpers :teardown_steps ()),
169+ rabbit_ct_helpers :testcase_finished (Config , Testcase );
150170end_per_testcase (Testcase , Config ) ->
151171 rabbit_ct_helpers :testcase_finished (Config , Testcase ).
152172
@@ -271,53 +291,145 @@ set_policy(Config) ->
271291delete_policy (Config ) ->
272292 ? assertError (_ , rabbit_ct_broker_helpers :clear_policy (Config , 0 , <<" policy-to-delete" >>)).
273293
274- add_node (Config ) ->
275- [A , B , C , D , _E ] = rabbit_ct_broker_helpers :get_node_configs (
294+ add_node_when_seed_node_is_leader (Config ) ->
295+ [A , B , C , _D , E ] = rabbit_ct_broker_helpers :get_node_configs (
276296 Config , nodename ),
277297
278298 % % Three node cluster: A, B, C
279- ok = rabbit_control_helper :command (stop_app , B ),
280- ok = rabbit_control_helper :command (join_cluster , B , [atom_to_list (A )], []),
281- rabbit_control_helper :command (start_app , B ),
299+ Cluster = [A , B , C ],
300+ Config1 = rabbit_ct_broker_helpers :cluster_nodes (Config , Cluster ),
282301
283- ok = rabbit_control_helper :command (stop_app , C ),
284- ok = rabbit_control_helper :command (join_cluster , C , [atom_to_list (A )], []),
285- rabbit_control_helper :command (start_app , C ),
302+ AMember = {rabbit_khepri :get_store_id (), A },
303+ _ = ra :transfer_leadership (AMember , AMember ),
286304
287305 % % Minority partition: A
306+ partition_3_node_cluster (Config1 ),
307+
308+ Pong = ra :ping (AMember , 10000 ),
309+ ct :pal (" Member A state: ~0p " , [Pong ]),
310+ case Pong of
311+ {pong , State } when State =/= follower andalso State =/= candidate ->
312+ Ret = rabbit_control_helper :command (
313+ join_cluster , E , [atom_to_list (A )], []),
314+ ? assertMatch ({error , _ , _ }, Ret ),
315+ {error , _ , Msg } = Ret ,
316+ ? assertEqual (
317+ match ,
318+ re :run (
319+ Msg , " \\ {:rabbit, \\ {\\ {:error, :timeout\\ }" ,
320+ [{capture , none }]));
321+ Ret ->
322+ ct :pal (" A is not the expected leader: ~p " , [Ret ]),
323+ {skip , " Node A was not elected leader" }
324+ end .
325+
326+ add_node_when_seed_node_is_follower (Config ) ->
327+ [A , B , C , _D , E ] = rabbit_ct_broker_helpers :get_node_configs (
328+ Config , nodename ),
329+
330+ % % Three node cluster: A, B, C
288331 Cluster = [A , B , C ],
289- partition_3_node_cluster (Config ),
290-
291- ok = rabbit_control_helper :command (stop_app , D ),
292- % % The command is appended to the log, but it will be dropped once the connectivity
293- % % is restored
294- ? assertMatch (ok ,
295- rabbit_control_helper :command (join_cluster , D , [atom_to_list (A )], [])),
296- timer :sleep (10000 ),
297- join_3_node_cluster (Config ),
298- clustering_utils :assert_cluster_status ({Cluster , Cluster }, Cluster ).
299-
300- remove_node (Config ) ->
332+ Config1 = rabbit_ct_broker_helpers :cluster_nodes (Config , Cluster ),
333+
334+ CMember = {rabbit_khepri :get_store_id (), C },
335+ ra :transfer_leadership (CMember , CMember ),
336+
337+ % % Minority partition: A
338+ partition_3_node_cluster (Config1 ),
339+
340+ AMember = {rabbit_khepri :get_store_id (), A },
341+ Pong = ra :ping (AMember , 10000 ),
342+ ct :pal (" Member A state: ~0p " , [Pong ]),
343+ case Pong of
344+ {pong , State }
345+ when State =:= follower orelse State =:= pre_vote ->
346+ Ret = rabbit_control_helper :command (
347+ join_cluster , E , [atom_to_list (A )], []),
348+ ? assertMatch ({error , _ , _ }, Ret ),
349+ {error , _ , Msg } = Ret ,
350+ ? assertEqual (
351+ match ,
352+ re :run (
353+ Msg , " Khepri cluster could be in minority" ,
354+ [{capture , none }]));
355+ {pong , await_condition } ->
356+ Ret = rabbit_control_helper :command (
357+ join_cluster , E , [atom_to_list (A )], []),
358+ ? assertMatch ({error , _ , _ }, Ret ),
359+ {error , _ , Msg } = Ret ,
360+ ? assertEqual (
361+ match ,
362+ re :run (
363+ Msg , " \\ {:rabbit, \\ {\\ {:error, :timeout\\ }" ,
364+ [{capture , none }]));
365+ Ret ->
366+ ct :pal (" A is not the expected follower: ~p " , [Ret ]),
367+ {skip , " Node A was not a follower" }
368+ end .
369+
370+ remove_node_when_seed_node_is_leader (Config ) ->
301371 [A , B , C | _ ] = rabbit_ct_broker_helpers :get_node_configs (
302372 Config , nodename ),
303373
304374 % % Three node cluster: A, B, C
305- ok = rabbit_control_helper :command (stop_app , B ),
306- ok = rabbit_control_helper :command (join_cluster , B , [atom_to_list (A )], []),
307- rabbit_control_helper :command (start_app , B ),
375+ Cluster = [A , B , C ],
376+ Config1 = rabbit_ct_broker_helpers :cluster_nodes (Config , Cluster ),
308377
309- ok = rabbit_control_helper :command (stop_app , C ),
310- ok = rabbit_control_helper :command (join_cluster , C , [atom_to_list (A )], []),
311- rabbit_control_helper :command (start_app , C ),
378+ AMember = {rabbit_khepri :get_store_id (), A },
379+ ra :transfer_leadership (AMember , AMember ),
312380
313381 % % Minority partition: A
314- partition_3_node_cluster (Config ),
382+ partition_3_node_cluster (Config1 ),
383+
384+ Pong = ra :ping (AMember , 10000 ),
385+ ct :pal (" Member A state: ~0p " , [Pong ]),
386+ case Pong of
387+ {pong , leader } ->
388+ Ret = rabbit_control_helper :command (
389+ forget_cluster_node , A , [atom_to_list (B )], []),
390+ ? assertEqual (ok , Ret );
391+ Ret ->
392+ ct :pal (" A is not the expected leader: ~p " , [Ret ]),
393+ {skip , " Node A was not a leader" }
394+ end .
395+
396+ remove_node_when_seed_node_is_follower (Config ) ->
397+ [A , B , C | _ ] = rabbit_ct_broker_helpers :get_node_configs (
398+ Config , nodename ),
399+
400+ % % Three node cluster: A, B, C
315401 Cluster = [A , B , C ],
402+ Config1 = rabbit_ct_broker_helpers :cluster_nodes (Config , Cluster ),
403+
404+ CMember = {rabbit_khepri :get_store_id (), C },
405+ ra :transfer_leadership (CMember , CMember ),
316406
317- ok = rabbit_control_helper :command (forget_cluster_node , A , [atom_to_list (B )], []),
318- timer :sleep (10000 ),
319- join_3_node_cluster (Config ),
320- clustering_utils :assert_cluster_status ({Cluster , Cluster }, Cluster ).
407+ % % Minority partition: A
408+ partition_3_node_cluster (Config1 ),
409+
410+ AMember = {rabbit_khepri :get_store_id (), A },
411+ Pong = ra :ping (AMember , 10000 ),
412+ ct :pal (" Member A state: ~0p " , [Pong ]),
413+ case Pong of
414+ {pong , State }
415+ when State =:= follower orelse State =:= pre_vote ->
416+ Ret = rabbit_control_helper :command (
417+ forget_cluster_node , A , [atom_to_list (B )], []),
418+ ? assertMatch ({error , _ , _ }, Ret ),
419+ {error , _ , Msg } = Ret ,
420+ ? assertEqual (
421+ match ,
422+ re :run (
423+ Msg , " Khepri cluster could be in minority" ,
424+ [{capture , none }]));
425+ {pong , await_condition } ->
426+ Ret = rabbit_control_helper :command (
427+ forget_cluster_node , A , [atom_to_list (B )], []),
428+ ? assertMatch (ok , Ret );
429+ Ret ->
430+ ct :pal (" A is not the expected leader: ~p " , [Ret ]),
431+ {skip , " Node A was not a leader" }
432+ end .
321433
322434enable_feature_flag (Config ) ->
323435 [A | _ ] = rabbit_ct_broker_helpers :get_node_configs (Config , nodename ),
0 commit comments