1111-include_lib (" common_test/include/ct.hrl" ).
1212-include_lib (" eunit/include/eunit.hrl" ).
1313
14+ -include_lib (" rabbitmq_ct_helpers/include/rabbit_assert.hrl" ).
15+
1416-compile ([nowarn_export_all , export_all ]).
1517
1618-define (VHOST , <<" /" >>).
1719
1820all () ->
1921 [
20- {group , mnesia_store }
22+ {group , mnesia_store },
23+ {group , khepri_store }
2124 ].
2225
2326groups () ->
2427 [
2528 {mnesia_store , [], mnesia_tests ()},
29+ {khepri_store , [], khepri_tests ()},
2630 {benchmarks , [], benchmarks ()}
2731 ].
2832
@@ -40,6 +44,11 @@ mnesia_tests() ->
4044 build_multiple_key_from_deletion_events
4145 ].
4246
47+ khepri_tests () ->
48+ [
49+ topic_trie_cleanup
50+ ].
51+
4352benchmarks () ->
4453 [
4554 match_benchmark
@@ -53,15 +62,26 @@ end_per_suite(Config) ->
5362 rabbit_ct_helpers :run_teardown_steps (Config ).
5463
5564init_per_group (mnesia_store = Group , Config0 ) ->
56- Config = rabbit_ct_helpers :set_config (Config0 , [{metadata_store , mnesia }]),
65+ Config = rabbit_ct_helpers :set_config (
66+ Config0 ,
67+ [{metadata_store , mnesia },
68+ {rmq_nodes_count , 1 }]),
69+ init_per_group_common (Group , Config );
70+ init_per_group (khepri_store = Group , Config0 ) ->
71+ Config = rabbit_ct_helpers :set_config (
72+ Config0 ,
73+ [{metadata_store , khepri },
74+ {rmq_nodes_count , 3 }]),
5775 init_per_group_common (Group , Config );
58- init_per_group (Group , Config ) ->
76+ init_per_group (Group , Config0 ) ->
77+ Config = rabbit_ct_helpers :set_config (
78+ Config0 ,
79+ [{rmq_nodes_count , 1 }]),
5980 init_per_group_common (Group , Config ).
6081
6182init_per_group_common (Group , Config ) ->
6283 Config1 = rabbit_ct_helpers :set_config (Config , [
63- {rmq_nodename_suffix , Group },
64- {rmq_nodes_count , 1 }
84+ {rmq_nodename_suffix , Group }
6585 ]),
6686 rabbit_ct_helpers :run_steps (Config1 ,
6787 rabbit_ct_broker_helpers :setup_steps () ++
@@ -375,6 +395,192 @@ build_multiple_key_from_deletion_events1(Config) ->
375395 lists :sort ([RK || {_ , RK } <- rabbit_db_topic_exchange :trie_records_to_key (Records )])),
376396 passed .
377397
398+ % % ---------------------------------------------------------------------------
399+ % % Khepri-specific Tests
400+ % % ---------------------------------------------------------------------------
401+
402+ % https://github.com/rabbitmq/rabbitmq-server/issues/15024
403+ topic_trie_cleanup (Config ) ->
404+ [_ , OldNode , NewNode ] = Nodes = rabbit_ct_broker_helpers :get_node_configs (Config , nodename ),
405+
406+ % % this test has to be isolated to avoid flakes
407+ VHost = <<" test-vhost-topic-trie" >>,
408+ ok = rabbit_ct_broker_helpers :rpc (Config , OldNode , rabbit_vhost , add , [VHost , <<" test-user" >>]),
409+
410+ % % Create an exchange in the vhost
411+ ExchangeName = rabbit_misc :r (VHost , exchange , <<" test-topic-exchange" >>),
412+ {ok , _Exchange } = rabbit_ct_broker_helpers :rpc (Config , OldNode , rabbit_exchange , declare ,
413+ [ExchangeName , topic , _Durable = true , _AutoDelete = false ,
414+ _Internal = false , _Args = [], <<" test-user" >>]),
415+
416+ % % List of routing keys that exercise topic exchange functionality
417+ RoutingKeys = [
418+ % % Exact patterns with common prefixes
419+ <<" a.b.c" >>,
420+ <<" a.b.d" >>,
421+ <<" a.b.e" >>,
422+ <<" a.c.d" >>,
423+ <<" a.c.e" >>,
424+ <<" b.c.d" >>,
425+ % % Patterns with a single wildcard
426+ <<" a.*.c" >>,
427+ <<" a.*.d" >>,
428+ <<" *.b.c" >>,
429+ <<" *.b.d" >>,
430+ <<" a.b.*" >>,
431+ <<" a.c.*" >>,
432+ <<" *.*" >>,
433+ <<" a.*" >>,
434+ <<" *.b" >>,
435+ <<" *" >>,
436+ % % Patterns with multiple wildcards
437+ <<" a.#" >>,
438+ <<" a.b.#" >>,
439+ <<" a.c.#" >>,
440+ <<" #.c" >>,
441+ <<" #.b.c" >>,
442+ <<" #.b.d" >>,
443+ <<" #" >>,
444+ <<" #.#" >>,
445+ % % Mixed patterns
446+ <<" a.*.#" >>,
447+ <<" *.b.#" >>,
448+ <<" *.#" >>,
449+ <<" #.*" >>,
450+ <<" #.*.#" >>,
451+ % % More complex patterns with common prefixes
452+ <<" orders.created.#" >>,
453+ <<" orders.updated.#" >>,
454+ <<" orders.*.confirmed" >>,
455+ <<" orders.#" >>,
456+ <<" events.user.#" >>,
457+ <<" events.system.#" >>,
458+ <<" events.#" >>
459+ ],
460+
461+ % % Shuffle the routing keys to test in random order
462+ ShuffledRoutingKeys = [RK || {_ , RK } <- lists :sort ([{rand :uniform (), RK } || RK <- RoutingKeys ])],
463+
464+ % % Create bindings for all routing keys
465+ Bindings = [begin
466+ QueueName = rabbit_misc :r (VHost , queue ,
467+ list_to_binary (" queue-" ++ integer_to_list (Idx ))),
468+ Ret = rabbit_ct_broker_helpers :rpc (
469+ Config , OldNode ,
470+ rabbit_amqqueue , declare , [QueueName , true , false , [], self (), <<" test-user" >>]),
471+ case Ret of
472+ {new , _Q } -> ok ;
473+ {existing , _Q } -> ok
474+ end ,
475+ # binding {source = ExchangeName ,
476+ key = RoutingKey ,
477+ destination = QueueName ,
478+ args = []}
479+ end || {Idx , RoutingKey } <- lists :enumerate (ShuffledRoutingKeys )],
480+
481+ % % Add all bindings
482+ [ok = rabbit_ct_broker_helpers :rpc (Config , OldNode , rabbit_binding , add , [B , <<" test-user" >>])
483+ || B <- Bindings ],
484+
485+ % % Log entries that were added to the ETS table
486+ lists :foreach (
487+ fun (Node ) ->
488+ VHostEntriesAfterAdd = read_topic_trie_table (Config , Node , VHost , rabbit_khepri_topic_trie_v2 ),
489+ ct :pal (" Bindings added on node ~s : ~p , ETS entries after add: ~p~n " ,
490+ [Node , length (Bindings ), length (VHostEntriesAfterAdd )])
491+ end , Nodes ),
492+
493+ % % Shuffle bindings again for deletion in random order
494+ ShuffledBindings = [B || {_ , B } <- lists :sort ([{rand :uniform (), B } || B <- Bindings ])],
495+
496+ % % Delete all bindings in random order
497+ [ok = rabbit_ct_broker_helpers :rpc (Config , OldNode , rabbit_binding , remove , [B , <<" test-user" >>])
498+ || B <- ShuffledBindings ],
499+
500+ % % Verify that the projection ETS table doesn't contain any entries related
501+ % % to this vhost
502+ try
503+ lists :foreach (
504+ fun (Node ) ->
505+ % % We read and check the new projection table only. It is
506+ % % declared by the new node and is available everywhere. The
507+ % % old projection table might be there in case of
508+ % % mixed-version testing. This part will be tested in the
509+ % % second part of the testcase.
510+ VHostEntriesAfterDelete = read_topic_trie_table (Config , Node , VHost , rabbit_khepri_topic_trie_v2 ),
511+ ct :pal (" ETS entries after delete on node ~s : ~p~n " , [Node , length (VHostEntriesAfterDelete )]),
512+
513+ % % Assert that no entries were found for this vhost after deletion
514+ ? assertEqual ([], VHostEntriesAfterDelete )
515+ end , Nodes ),
516+
517+ % % If we reach this point, we know the new projection works as expected
518+ % % and the leaked ETS entries are no more.
519+ % %
520+ % % Now, we want to test that after an upgrade, the old projection is
521+ % % unregistered.
522+ HasOldProjection = try
523+ VHostEntriesInOldTable = read_topic_trie_table (
524+ Config , OldNode , VHost , rabbit_khepri_topic_trie ),
525+ ct :pal (" Old ETS table entries after delete: ~p~n " , [length (VHostEntriesInOldTable )]),
526+ ? assertNotEqual ([], VHostEntriesInOldTable ),
527+ true
528+ catch
529+ error :{exception , badarg , _ } ->
530+ % % The old projection doesn't exist. The old
531+ % % node, if we are in a mixed-version test,
532+ % % also supports the new projection. There
533+ % % is nothing more to test.
534+ ct :pal (" The old projection was not registered, nothing to test" ),
535+ false
536+ end ,
537+
538+ case HasOldProjection of
539+ true ->
540+ % % The old projection is registered. Simulate an update by removing
541+ % % node 1 (which is the old one in our mixed-version testing) from
542+ % % the cluster, then restart node 2. On restart, it should
543+ % % unregister the old projection.
544+ % %
545+ % % FIXME: The cluster is configured at the test group level.
546+ % % Therefore, if we add more testcases to this group, following
547+ % % testcases won't have the expected cluster.
548+ ? assertEqual (ok , rabbit_ct_broker_helpers :stop_broker (Config , OldNode )),
549+ ? assertEqual (ok , rabbit_ct_broker_helpers :forget_cluster_node (Config , NewNode , OldNode )),
550+
551+ ct :pal (" Restart new node (node 2)" ),
552+ ? assertEqual (ok , rabbit_ct_broker_helpers :restart_broker (Config , NewNode )),
553+
554+ ct :pal (" Wait for projections to be restored" ),
555+ ? awaitMatch (
556+ Entries when is_list (Entries ),
557+ catch read_topic_trie_table (Config , NewNode , VHost , rabbit_khepri_topic_trie_v2 ),
558+ 60000 ),
559+
560+ ct :pal (" Check that the old projection is gone" ),
561+ ? assertError (
562+ {exception , badarg , _ },
563+ read_topic_trie_table (Config , NewNode , VHost , rabbit_khepri_topic_trie ));
564+ false ->
565+ ok
566+ end
567+ after
568+ % % Clean up the vhost
569+ ok = rabbit_ct_broker_helpers :rpc (Config , NewNode , rabbit_vhost , delete , [VHost , <<" test-user" >>])
570+ end ,
571+
572+ passed .
573+
574+ read_topic_trie_table (Config , Node , VHost , Table ) ->
575+ Entries = rabbit_ct_broker_helpers :rpc (Config , Node , ets , tab2list , [Table ]),
576+ [Entry || # topic_trie_edge {trie_edge = TrieEdge } = Entry <- Entries ,
577+ case TrieEdge of
578+ # trie_edge {exchange_name = # resource {virtual_host = V }} ->
579+ V =:= VHost ;
580+ _ ->
581+ false
582+ end ].
583+
378584% % ---------------------------------------------------------------------------
379585% % Benchmarks
380586% % ---------------------------------------------------------------------------
0 commit comments