99
1010-include_lib (" amqp_client/include/amqp_client.hrl" ).
1111-include_lib (" eunit/include/eunit.hrl" ).
12+ -include_lib (" rabbitmq_ct_helpers/include/rabbit_assert.hrl" ).
1213
1314-compile ([export_all , nowarn_export_all ]).
1415
1516all () ->
1617 [
1718 {group , client_operations },
18- {group , cluster_operation_add },
19- {group , cluster_operation_remove }
19+ {group , cluster_operation }
2020 ].
2121
2222groups () ->
@@ -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,49 @@ 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
142- end_per_group (_ , Config ) ->
142+ end_per_group (Group , Config ) when Group == client_operations ;
143+ Group == feature_flags ->
143144 rabbit_ct_helpers :run_steps (Config ,
144145 rabbit_ct_client_helpers :teardown_steps () ++
145- rabbit_ct_broker_helpers :teardown_steps ()).
146-
146+ rabbit_ct_broker_helpers :teardown_steps ());
147+ end_per_group (_Group , Config ) ->
148+ Config .
149+
150+ init_per_testcase (Testcase , Config )
151+ when Testcase =:= add_node_when_seed_node_is_leader orelse
152+ Testcase =:= add_node_when_seed_node_is_follower orelse
153+ Testcase =:= remove_node_when_seed_node_is_leader orelse
154+ Testcase =:= remove_node_when_seed_node_is_follower ->
155+ rabbit_ct_helpers :testcase_started (Config , Testcase ),
156+ Config1 = rabbit_ct_helpers :set_config (
157+ Config , [{rmq_nodename_suffix , Testcase }]),
158+ rabbit_ct_helpers :run_steps (
159+ Config1 ,
160+ rabbit_ct_broker_helpers :setup_steps () ++
161+ rabbit_ct_client_helpers :setup_steps ());
147162init_per_testcase (Testcase , Config ) ->
148163 rabbit_ct_helpers :testcase_started (Config , Testcase ).
149164
165+ end_per_testcase (Testcase , Config )
166+ when Testcase =:= add_node_when_seed_node_is_leader orelse
167+ Testcase =:= add_node_when_seed_node_is_follower orelse
168+ Testcase =:= remove_node_when_seed_node_is_leader orelse
169+ Testcase =:= remove_node_when_seed_node_is_follower ->
170+ rabbit_ct_helpers :run_steps (
171+ Config ,
172+ rabbit_ct_client_helpers :teardown_steps () ++
173+ rabbit_ct_broker_helpers :teardown_steps ()),
174+ rabbit_ct_helpers :testcase_finished (Config , Testcase );
150175end_per_testcase (Testcase , Config ) ->
151176 rabbit_ct_helpers :testcase_finished (Config , Testcase ).
152177
@@ -271,53 +296,151 @@ set_policy(Config) ->
271296delete_policy (Config ) ->
272297 ? assertError (_ , rabbit_ct_broker_helpers :clear_policy (Config , 0 , <<" policy-to-delete" >>)).
273298
274- add_node (Config ) ->
275- [A , B , C , D , _E ] = rabbit_ct_broker_helpers :get_node_configs (
299+ add_node_when_seed_node_is_leader (Config ) ->
300+ [A , B , C , _D , E ] = rabbit_ct_broker_helpers :get_node_configs (
276301 Config , nodename ),
277302
278303 % % 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 ),
304+ Cluster = [A , B , C ],
305+ Config1 = rabbit_ct_broker_helpers :cluster_nodes (Config , Cluster ),
282306
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 ),
307+ AMember = { rabbit_khepri : get_store_id (), A } ,
308+ _ = ra : transfer_leadership ( AMember , AMember ),
309+ clustering_utils : assert_cluster_status ({ Cluster , Cluster }, Cluster ),
286310
287311 % % Minority partition: A
312+ partition_3_node_cluster (Config1 ),
313+
314+ Pong = ra :ping (AMember , 10000 ),
315+ ct :pal (" Member A state: ~0p " , [Pong ]),
316+ case Pong of
317+ {pong , State } when State =/= follower andalso State =/= candidate ->
318+ Ret = rabbit_control_helper :command (
319+ join_cluster , E , [atom_to_list (A )], []),
320+ ? assertMatch ({error , _ , _ }, Ret ),
321+ {error , _ , Msg } = Ret ,
322+ ? assertEqual (
323+ match ,
324+ re :run (
325+ Msg , " (Khepri cluster could be in minority|\\ {:rabbit, \\ {\\ {:error, :timeout\\ })" ,
326+ [{capture , none }]));
327+ Ret ->
328+ ct :pal (" A is not the expected leader: ~p " , [Ret ]),
329+ {skip , " Node A was not elected leader" }
330+ end .
331+
332+ add_node_when_seed_node_is_follower (Config ) ->
333+ [A , B , C , _D , E ] = rabbit_ct_broker_helpers :get_node_configs (
334+ Config , nodename ),
335+
336+ % % Three node cluster: A, B, C
288337 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 ) ->
338+ Config1 = rabbit_ct_broker_helpers :cluster_nodes (Config , Cluster ),
339+
340+ CMember = {rabbit_khepri :get_store_id (), C },
341+ ra :transfer_leadership (CMember , CMember ),
342+ clustering_utils :assert_cluster_status ({Cluster , Cluster }, Cluster ),
343+
344+ % % Minority partition: A
345+ partition_3_node_cluster (Config1 ),
346+
347+ AMember = {rabbit_khepri :get_store_id (), A },
348+ Pong = ra :ping (AMember , 10000 ),
349+ ct :pal (" Member A state: ~0p " , [Pong ]),
350+ case Pong of
351+ {pong , State }
352+ when State =:= follower orelse State =:= pre_vote ->
353+ Ret = rabbit_control_helper :command (
354+ join_cluster , E , [atom_to_list (A )], []),
355+ ? assertMatch ({error , _ , _ }, Ret ),
356+ {error , _ , Msg } = Ret ,
357+ ? assertEqual (
358+ match ,
359+ re :run (
360+ Msg , " Khepri cluster could be in minority" ,
361+ [{capture , none }]));
362+ {pong , await_condition } ->
363+ Ret = rabbit_control_helper :command (
364+ join_cluster , E , [atom_to_list (A )], []),
365+ ? assertMatch ({error , _ , _ }, Ret ),
366+ {error , _ , Msg } = Ret ,
367+ ? assertEqual (
368+ match ,
369+ re :run (
370+ Msg , " \\ {:rabbit, \\ {\\ {:error, :timeout\\ }" ,
371+ [{capture , none }])),
372+ clustering_utils :assert_cluster_status (
373+ {Cluster , Cluster }, Cluster );
374+ Ret ->
375+ ct :pal (" A is not the expected follower: ~p " , [Ret ]),
376+ {skip , " Node A was not a follower" }
377+ end .
378+
379+ remove_node_when_seed_node_is_leader (Config ) ->
301380 [A , B , C | _ ] = rabbit_ct_broker_helpers :get_node_configs (
302381 Config , nodename ),
303382
304383 % % 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 ),
384+ Cluster = [A , B , C ],
385+ Config1 = rabbit_ct_broker_helpers :cluster_nodes (Config , Cluster ),
308386
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 ),
387+ AMember = {rabbit_khepri :get_store_id (), A },
388+ ra :transfer_leadership (AMember , AMember ),
389+ clustering_utils :assert_cluster_status ({Cluster , Cluster }, Cluster ),
390+ ct :pal (" Waiting for cluster change permitted on node A" ),
391+ ? awaitMatch (
392+ {ok , #{cluster_change_permitted := true ,
393+ leader_id := AMember }, AMember },
394+ rabbit_ct_broker_helpers :rpc (
395+ Config1 , A , ra , member_overview , [AMember ]),
396+ 60000 ),
397+ {ok , Overview , AMember } = rabbit_ct_broker_helpers :rpc (
398+ Config1 , A , ra , member_overview , [AMember ]),
399+ ct :pal (" Member A overview: ~p " , [maps :remove (machine , Overview )]),
312400
313401 % % Minority partition: A
314- partition_3_node_cluster (Config ),
402+ partition_3_node_cluster (Config1 ),
403+
404+ ? assertEqual ({pong , leader }, ra :ping (AMember , 10000 )),
405+ ? awaitMatch (
406+ ok ,
407+ rabbit_control_helper :command (
408+ forget_cluster_node , A , [atom_to_list (B )], []),
409+ 60000 ).
410+
411+ remove_node_when_seed_node_is_follower (Config ) ->
412+ [A , B , C | _ ] = rabbit_ct_broker_helpers :get_node_configs (
413+ Config , nodename ),
414+
415+ % % Three node cluster: A, B, C
315416 Cluster = [A , B , C ],
417+ Config1 = rabbit_ct_broker_helpers :cluster_nodes (Config , Cluster ),
316418
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 ).
419+ AMember = {rabbit_khepri :get_store_id (), A },
420+ CMember = {rabbit_khepri :get_store_id (), C },
421+ ra :transfer_leadership (CMember , CMember ),
422+ clustering_utils :assert_cluster_status ({Cluster , Cluster }, Cluster ),
423+ ? awaitMatch (
424+ {ok , #{cluster_change_permitted := true ,
425+ leader_id := CMember }, AMember },
426+ rabbit_ct_broker_helpers :rpc (
427+ Config1 , A , ra , member_overview , [AMember ]),
428+ 60000 ),
429+ {ok , Overview , AMember } = rabbit_ct_broker_helpers :rpc (
430+ Config1 , A , ra , member_overview , [AMember ]),
431+ ct :pal (" Member A overview: ~p " , [maps :remove (machine , Overview )]),
432+
433+ % % Minority partition: A
434+ partition_3_node_cluster (Config1 ),
435+
436+ Ret = rabbit_control_helper :command (
437+ forget_cluster_node , A , [atom_to_list (B )], []),
438+ ? assertMatch ({error , _ , _ }, Ret ),
439+ {error , _ , Msg } = Ret ,
440+ ? assertEqual (
441+ match ,
442+ re :run (
443+ Msg , " Khepri cluster could be in minority" , [{capture , none }])).
321444
322445enable_feature_flag (Config ) ->
323446 [A | _ ] = rabbit_ct_broker_helpers :get_node_configs (Config , nodename ),
0 commit comments