@@ -118,6 +118,7 @@ groups() ->
118
118
modified_classic_queue ,
119
119
modified_quorum_queue ,
120
120
modified_dead_letter_headers_exchange ,
121
+ modified_dead_letter_history ,
121
122
dead_letter_headers_exchange ,
122
123
dead_letter_reject ,
123
124
dead_letter_reject_message_order_classic_queue ,
@@ -253,7 +254,8 @@ init_per_testcase(T, Config)
253
254
end ;
254
255
init_per_testcase (T , Config )
255
256
when T =:= modified_quorum_queue orelse
256
- T =:= modified_dead_letter_headers_exchange ->
257
+ T =:= modified_dead_letter_headers_exchange orelse
258
+ T =:= modified_dead_letter_history ->
257
259
case rpc (Config , rabbit_feature_flags , is_enabled , ['rabbitmq_4.0.0' ]) of
258
260
true ->
259
261
rabbit_ct_helpers :testcase_started (Config , T );
@@ -490,79 +492,127 @@ modified_quorum_queue(Config) ->
490
492
ok = amqp10_client :send_msg (Sender , Msg2 ),
491
493
ok = amqp10_client :detach_link (Sender ),
492
494
493
- {ok , Receiver } = amqp10_client :attach_receiver_link (Session , <<" receiver" >>, Address , unsettled ),
495
+ Receiver1Name = <<" receiver 1" >>,
496
+ Receiver2Name = <<" receiver 2" >>,
497
+ {ok , Receiver1 } = amqp10_client :attach_receiver_link (Session , Receiver1Name , Address , unsettled ),
498
+ {ok , Receiver2 } = amqp10_client :attach_receiver_link (Session , Receiver2Name , Address , unsettled ),
494
499
495
- {ok , M1 } = amqp10_client :get_msg (Receiver ),
500
+ {ok , M1 } = amqp10_client :get_msg (Receiver1 ),
496
501
? assertEqual ([<<" m1" >>], amqp10_msg :body (M1 )),
497
502
? assertMatch (#{delivery_count := 0 ,
498
503
first_acquirer := true },
499
504
amqp10_msg :headers (M1 )),
500
- ok = amqp10_client :settle_msg (Receiver , M1 , {modified , false , true , #{}}),
505
+ ok = amqp10_client :settle_msg (Receiver1 , M1 , {modified , false , true , #{}}),
501
506
502
- {ok , M2a } = amqp10_client :get_msg (Receiver ),
507
+ {ok , M2a } = amqp10_client :get_msg (Receiver1 ),
503
508
? assertEqual ([<<" m2" >>], amqp10_msg :body (M2a )),
504
509
? assertMatch (#{delivery_count := 0 ,
505
510
first_acquirer := true },
506
511
amqp10_msg :headers (M2a )),
507
- ok = amqp10_client :settle_msg (Receiver , M2a , {modified , false , false , #{}}),
512
+ ok = amqp10_client :settle_msg (Receiver1 , M2a , {modified , false , false , #{}}),
508
513
509
- {ok , M2b } = amqp10_client :get_msg (Receiver ),
514
+ {ok , M2b } = amqp10_client :get_msg (Receiver1 ),
510
515
? assertEqual ([<<" m2" >>], amqp10_msg :body (M2b )),
511
516
? assertMatch (#{delivery_count := 0 ,
512
517
first_acquirer := false },
513
518
amqp10_msg :headers (M2b )),
514
- ok = amqp10_client :settle_msg (Receiver , M2b , {modified , true , false , #{}}),
519
+ ok = amqp10_client :settle_msg (Receiver1 , M2b , {modified , true , false , #{}}),
515
520
516
- {ok , M2c } = amqp10_client :get_msg (Receiver ),
521
+ {ok , M2c } = amqp10_client :get_msg (Receiver1 ),
517
522
? assertEqual ([<<" m2" >>], amqp10_msg :body (M2c )),
518
523
? assertMatch (#{delivery_count := 1 ,
519
524
first_acquirer := false },
520
525
amqp10_msg :headers (M2c )),
521
- ok = amqp10_client :settle_msg (Receiver , M2c ,
522
- {modified , true , false ,
523
- #{<<" x-opt-key" >> => <<" val 1" >>}}),
524
-
525
- {ok , M2d } = amqp10_client :get_msg (Receiver ),
526
+ ok = amqp10_client :settle_msg (
527
+ Receiver1 , M2c ,
528
+ {modified , true , false ,
529
+ % % Test that a history of requeue events can be tracked as described in
530
+ % % https://rabbitmq.com/blog/2024/10/11/modified-outcome
531
+ #{<<" x-opt-requeued-by" >> => {array , utf8 , [{utf8 , Receiver1Name }]},
532
+ <<" x-opt-requeue-reason" >> => {list , [{utf8 , <<" reason 1" >>}]},
533
+ <<" x-opt-my-map" >> => {map , [
534
+ {{utf8 , <<" k1" >>}, {byte , - 1 }},
535
+ {{utf8 , <<" k2" >>}, {ulong , 2 }}
536
+ ]}}}),
537
+
538
+ {ok , M2d } = amqp10_client :get_msg (Receiver2 ),
526
539
? assertEqual ([<<" m2" >>], amqp10_msg :body (M2d )),
527
540
? assertMatch (#{delivery_count := 2 ,
528
541
first_acquirer := false },
529
542
amqp10_msg :headers (M2d )),
530
- ? assertMatch (#{<<" x-opt-key" >> := <<" val 1" >>}, amqp10_msg :message_annotations (M2d )),
531
- ok = amqp10_client :settle_msg (Receiver , M2d ,
532
- {modified , false , false ,
533
- #{<<" x-opt-key" >> => <<" val 2" >>,
534
- <<" x-other" >> => 99 }}),
535
-
536
- {ok , M2e } = amqp10_client :get_msg (Receiver ),
543
+ #{<<" x-opt-requeued-by" >> := {array , utf8 , L0 },
544
+ <<" x-opt-requeue-reason" >> := L1 ,
545
+ <<" x-opt-my-map" >> := L2 } = amqp10_msg :message_annotations (M2d ),
546
+ ok = amqp10_client :settle_msg (
547
+ Receiver1 , M2d ,
548
+ {modified , false , false ,
549
+ #{<<" x-opt-requeued-by" >> => {array , utf8 , [{utf8 , Receiver2Name } | L0 ]},
550
+ <<" x-opt-requeue-reason" >> => {list , [{symbol , <<" reason 2" >>} | L1 ]},
551
+ <<" x-opt-my-map" >> => {map , L2 ++ [{{symbol , <<" k3" >>}, {symbol , <<" val 3" >>}}]},
552
+ <<" x-other" >> => 99 }}),
553
+
554
+ {ok , M2e } = amqp10_client :get_msg (Receiver1 ),
537
555
? assertEqual ([<<" m2" >>], amqp10_msg :body (M2e )),
538
556
? assertMatch (#{delivery_count := 2 ,
539
557
first_acquirer := false },
540
558
amqp10_msg :headers (M2e )),
541
- ? assertMatch (#{<<" x-opt-key" >> := <<" val 2" >>,
559
+ ? assertMatch (#{<<" x-opt-requeued-by" >> := {array , utf8 , [{utf8 , Receiver2Name }, {utf8 , Receiver1Name }]},
560
+ <<" x-opt-requeue-reason" >> := [{symbol , <<" reason 2" >>}, {utf8 , <<" reason 1" >>}],
561
+ <<" x-opt-my-map" >> := [
562
+ {{utf8 , <<" k1" >>}, {byte , - 1 }},
563
+ {{utf8 , <<" k2" >>}, {ulong , 2 }},
564
+ {{symbol , <<" k3" >>}, {symbol , <<" val 3" >>}}
565
+ ],
542
566
<<" x-other" >> := 99 }, amqp10_msg :message_annotations (M2e )),
543
- ok = amqp10_client :settle_msg (Receiver , M2e , modified ),
567
+ ok = amqp10_client :settle_msg (Receiver1 , M2e , modified ),
544
568
545
- ok = amqp10_client :detach_link (Receiver ),
546
- ? assertMatch ({ok , #{message_count := 1 }},
547
- rabbitmq_amqp_client :delete_queue (LinkPair , QName )),
569
+ % % Test that we can consume via AMQP 0.9.1
570
+ Ch = rabbit_ct_client_helpers :open_channel (Config ),
571
+ {# 'basic.get_ok' {},
572
+ # amqp_msg {payload = <<" m2" >>,
573
+ props = # 'P_basic' {headers = Headers }}
574
+ } = amqp_channel :call (Ch , # 'basic.get' {queue = QName , no_ack = true }),
575
+ % % We expect to receive only modified AMQP 1.0 message annotations that are of simple types
576
+ % % (i.e. excluding list, map, array).
577
+ ? assertEqual ({value , {<<" x-other" >>, long , 99 }},
578
+ lists :keysearch (<<" x-other" >>, 1 , Headers )),
579
+ ? assertEqual ({value , {<<" x-delivery-count" >>, long , 5 }},
580
+ lists :keysearch (<<" x-delivery-count" >>, 1 , Headers )),
581
+ ok = rabbit_ct_client_helpers :close_channel (Ch ),
582
+
583
+ ok = amqp10_client :detach_link (Receiver1 ),
584
+ {ok , _ } = rabbitmq_amqp_client :delete_queue (LinkPair , QName ),
548
585
ok = rabbitmq_amqp_client :detach_management_link_pair_sync (LinkPair ),
549
586
ok = end_session_sync (Session ),
550
587
ok = amqp10_client :close_connection (Connection ).
551
588
552
589
% % Test that a message can be routed based on the message-annotations
553
- % % provided in the modified outcome.
590
+ % % provided in the modified outcome as described in
591
+ % % https://rabbitmq.com/blog/2024/10/11/modified-outcome
554
592
modified_dead_letter_headers_exchange (Config ) ->
555
593
{Connection , Session , LinkPair } = init (Config ),
594
+ HeadersXName = <<" my headers exchange" >>,
595
+ AlternateXName = <<" my alternate exchange" >>,
556
596
SourceQName = <<" source quorum queue" >>,
557
597
AppleQName = <<" dead letter classic queue receiving apples" >>,
558
598
BananaQName = <<" dead letter quorum queue receiving bananas" >>,
599
+ TrashQName = <<" trash queue receiving anything that doesn't match" >>,
600
+
601
+ ok = rabbitmq_amqp_client :declare_exchange (
602
+ LinkPair ,
603
+ HeadersXName ,
604
+ #{type => <<" headers" >>,
605
+ arguments => #{<<" alternate-exchange" >> => {utf8 , AlternateXName }}}),
606
+
607
+ ok = rabbitmq_amqp_client :declare_exchange (LinkPair , AlternateXName , #{type => <<" fanout" >>}),
608
+
559
609
{ok , #{type := <<" quorum" >>}} = rabbitmq_amqp_client :declare_queue (
560
610
LinkPair ,
561
611
SourceQName ,
562
612
#{arguments => #{<<" x-queue-type" >> => {utf8 , <<" quorum" >>},
563
613
<<" x-overflow" >> => {utf8 , <<" reject-publish" >>},
564
614
<<" x-dead-letter-strategy" >> => {utf8 , <<" at-least-once" >>},
565
- <<" x-dead-letter-exchange" >> => {utf8 , << " amq.headers " >> }}}),
615
+ <<" x-dead-letter-exchange" >> => {utf8 , HeadersXName }}}),
566
616
{ok , #{type := <<" classic" >>}} = rabbitmq_amqp_client :declare_queue (
567
617
LinkPair ,
568
618
AppleQName ,
@@ -571,14 +621,16 @@ modified_dead_letter_headers_exchange(Config) ->
571
621
LinkPair ,
572
622
BananaQName ,
573
623
#{arguments => #{<<" x-queue-type" >> => {utf8 , <<" quorum" >>}}}),
624
+ {ok , _ } = rabbitmq_amqp_client :declare_queue (LinkPair , TrashQName , #{}),
574
625
ok = rabbitmq_amqp_client :bind_queue (
575
- LinkPair , AppleQName , << " amq.headers " >> , <<>>,
626
+ LinkPair , AppleQName , HeadersXName , <<>>,
576
627
#{<<" x-fruit" >> => {utf8 , <<" apple" >>},
577
628
<<" x-match" >> => {utf8 , <<" any-with-x" >>}}),
578
629
ok = rabbitmq_amqp_client :bind_queue (
579
- LinkPair , BananaQName , << " amq.headers " >> , <<>>,
630
+ LinkPair , BananaQName , HeadersXName , <<>>,
580
631
#{<<" x-fruit" >> => {utf8 , <<" banana" >>},
581
632
<<" x-match" >> => {utf8 , <<" any-with-x" >>}}),
633
+ ok = rabbitmq_amqp_client :bind_queue (LinkPair , TrashQName , AlternateXName , <<>>, #{}),
582
634
583
635
{ok , Sender } = amqp10_client :attach_sender_link (
584
636
Session , <<" test-sender" >>, rabbitmq_amqp_address :queue (SourceQName )),
@@ -589,6 +641,8 @@ modified_dead_letter_headers_exchange(Config) ->
589
641
Session , <<" receiver apple" >>, rabbitmq_amqp_address :queue (AppleQName ), unsettled ),
590
642
{ok , ReceiverBanana } = amqp10_client :attach_receiver_link (
591
643
Session , <<" receiver banana" >>, rabbitmq_amqp_address :queue (BananaQName ), unsettled ),
644
+ {ok , ReceiverTrash } = amqp10_client :attach_receiver_link (
645
+ Session , <<" receiver trash" >>, rabbitmq_amqp_address :queue (TrashQName ), unsettled ),
592
646
593
647
ok = amqp10_client :send_msg (Sender , amqp10_msg :new (<<" t1" >>, <<" m1" >>)),
594
648
ok = amqp10_client :send_msg (Sender , amqp10_msg :new (<<" t2" >>, <<" m2" >>)),
@@ -598,7 +652,8 @@ modified_dead_letter_headers_exchange(Config) ->
598
652
ok = amqp10_client :send_msg (Sender , amqp10_msg :set_message_annotations (
599
653
#{" x-fruit" => <<" apple" >>},
600
654
amqp10_msg :new (<<" t4" >>, <<" m4" >>))),
601
- ok = wait_for_accepts (3 ),
655
+ ok = amqp10_client :send_msg (Sender , amqp10_msg :new (<<" t5" >>, <<" m5" >>)),
656
+ ok = wait_for_accepts (5 ),
602
657
603
658
{ok , Msg1 } = amqp10_client :get_msg (Receiver ),
604
659
? assertMatch (#{delivery_count := 0 ,
@@ -639,13 +694,105 @@ modified_dead_letter_headers_exchange(Config) ->
639
694
amqp10_msg :headers (MsgBanana2 )),
640
695
ok = amqp10_client :accept_msg (ReceiverBanana , MsgBanana2 ),
641
696
697
+ {ok , Msg5 } = amqp10_client :get_msg (Receiver ),
698
+ % % This message should be routed via the alternate exchange to the trash queue.
699
+ ok = amqp10_client :settle_msg (Receiver , Msg5 , {modified , false , true , #{<<" x-fruit" >> => <<" strawberry" >>}}),
700
+ {ok , MsgTrash } = amqp10_client :get_msg (ReceiverTrash ),
701
+ ? assertEqual ([<<" m5" >>], amqp10_msg :body (MsgTrash )),
702
+ ? assertMatch (#{delivery_count := 0 ,
703
+ first_acquirer := false },
704
+ amqp10_msg :headers (MsgTrash )),
705
+ ok = amqp10_client :accept_msg (ReceiverTrash , MsgTrash ),
706
+
642
707
ok = detach_link_sync (Sender ),
643
708
ok = detach_link_sync (Receiver ),
644
709
ok = detach_link_sync (ReceiverApple ),
645
710
ok = detach_link_sync (ReceiverBanana ),
646
711
{ok , #{message_count := 0 }} = rabbitmq_amqp_client :delete_queue (LinkPair , SourceQName ),
647
712
{ok , #{message_count := 0 }} = rabbitmq_amqp_client :delete_queue (LinkPair , AppleQName ),
648
713
{ok , #{message_count := 0 }} = rabbitmq_amqp_client :delete_queue (LinkPair , BananaQName ),
714
+ {ok , #{message_count := 0 }} = rabbitmq_amqp_client :delete_queue (LinkPair , TrashQName ),
715
+ ok = rabbitmq_amqp_client :delete_exchange (LinkPair , HeadersXName ),
716
+ ok = rabbitmq_amqp_client :delete_exchange (LinkPair , AlternateXName ),
717
+ ok = rabbitmq_amqp_client :detach_management_link_pair_sync (LinkPair ),
718
+ ok = end_session_sync (Session ),
719
+ ok = amqp10_client :close_connection (Connection ).
720
+
721
+ % % Test that custom dead lettering event tracking works as described in
722
+ % % https://rabbitmq.com/blog/2024/10/11/modified-outcome
723
+ modified_dead_letter_history (Config ) ->
724
+ {Connection , Session , LinkPair } = init (Config ),
725
+ Q1 = <<" qq 1" >>,
726
+ Q2 = <<" qq 2" >>,
727
+
728
+ {ok , _ } = rabbitmq_amqp_client :declare_queue (
729
+ LinkPair , Q1 ,
730
+ #{arguments => #{<<" x-queue-type" >> => {utf8 , <<" quorum" >>},
731
+ <<" x-dead-letter-strategy" >> => {utf8 , <<" at-most-once" >>},
732
+ <<" x-dead-letter-exchange" >> => {utf8 , <<" amq.fanout" >>}}}),
733
+ {ok , _ } = rabbitmq_amqp_client :declare_queue (
734
+ LinkPair , Q2 ,
735
+ #{arguments => #{<<" x-queue-type" >> => {utf8 , <<" quorum" >>},
736
+ <<" x-dead-letter-strategy" >> => {utf8 , <<" at-most-once" >>},
737
+ <<" x-dead-letter-exchange" >> => {utf8 , <<>>}}}),
738
+ ok = rabbitmq_amqp_client :bind_queue (LinkPair , Q2 , <<" amq.fanout" >>, <<>>, #{}),
739
+
740
+ {ok , Sender } = amqp10_client :attach_sender_link (
741
+ Session , <<" test-sender" >>, rabbitmq_amqp_address :queue (Q1 )),
742
+ wait_for_credit (Sender ),
743
+ {ok , Receiver1 } = amqp10_client :attach_receiver_link (
744
+ Session , <<" receiver 1" >>, rabbitmq_amqp_address :queue (Q1 ), unsettled ),
745
+ {ok , Receiver2 } = amqp10_client :attach_receiver_link (
746
+ Session , <<" receiver 2" >>, rabbitmq_amqp_address :queue (Q2 ), unsettled ),
747
+
748
+ ok = amqp10_client :send_msg (Sender , amqp10_msg :new (<<" t" >>, <<" m" >>)),
749
+ ok = wait_for_accepts (1 ),
750
+ ok = detach_link_sync (Sender ),
751
+
752
+ {ok , Msg1 } = amqp10_client :get_msg (Receiver1 ),
753
+ ? assertMatch (#{delivery_count := 0 ,
754
+ first_acquirer := true },
755
+ amqp10_msg :headers (Msg1 )),
756
+ ok = amqp10_client :settle_msg (
757
+ Receiver1 , Msg1 ,
758
+ {modified , true , true ,
759
+ #{<<" x-opt-history-list" >> => {list , [{utf8 , <<" l1" >>}]},
760
+ <<" x-opt-history-map" >> => {map , [{{symbol , <<" k1" >>}, {byte , - 1 }}]},
761
+ <<" x-opt-history-array" >> => {array , utf8 , [{utf8 , <<" a1" >>}]}}
762
+ }),
763
+
764
+ {ok , Msg2 } = amqp10_client :get_msg (Receiver2 ),
765
+ ? assertMatch (#{delivery_count := 1 ,
766
+ first_acquirer := false },
767
+ amqp10_msg :headers (Msg2 )),
768
+ #{<<" x-opt-history-list" >> := L1 ,
769
+ <<" x-opt-history-map" >> := L2 ,
770
+ <<" x-opt-history-array" >> := {array , utf8 , L0 }
771
+ } = amqp10_msg :message_annotations (Msg2 ),
772
+ ok = amqp10_client :settle_msg (
773
+ Receiver2 , Msg2 ,
774
+ {modified , true , true ,
775
+ #{<<" x-opt-history-list" >> => {list , [{int , - 99 } | L1 ]},
776
+ <<" x-opt-history-map" >> => {map , [{{symbol , <<" k2" >>}, {symbol , <<" v2" >>}} | L2 ]},
777
+ <<" x-opt-history-array" >> => {array , utf8 , [{utf8 , <<" a2" >>} | L0 ]},
778
+ <<" x-other" >> => - 99 }}),
779
+
780
+ {ok , Msg3 } = amqp10_client :get_msg (Receiver1 ),
781
+ ? assertEqual ([<<" m" >>], amqp10_msg :body (Msg3 )),
782
+ ? assertMatch (#{delivery_count := 2 ,
783
+ first_acquirer := false },
784
+ amqp10_msg :headers (Msg3 )),
785
+ ? assertMatch (#{<<" x-opt-history-array" >> := {array , utf8 , [{utf8 , <<" a2" >>}, {utf8 , <<" a1" >>}]},
786
+ <<" x-opt-history-list" >> := [{int , - 99 }, {utf8 , <<" l1" >>}],
787
+ <<" x-opt-history-map" >> := [{{symbol , <<" k2" >>}, {symbol , <<" v2" >>}},
788
+ {{symbol , <<" k1" >>}, {byte , - 1 }}],
789
+ <<" x-other" >> := - 99 }, amqp10_msg :message_annotations (Msg3 )),
790
+ ok = amqp10_client :accept_msg (Receiver1 , Msg3 ),
791
+
792
+ ok = detach_link_sync (Receiver1 ),
793
+ ok = detach_link_sync (Receiver2 ),
794
+ {ok , #{message_count := 0 }} = rabbitmq_amqp_client :delete_queue (LinkPair , Q1 ),
795
+ {ok , #{message_count := 0 }} = rabbitmq_amqp_client :delete_queue (LinkPair , Q2 ),
649
796
ok = rabbitmq_amqp_client :detach_management_link_pair_sync (LinkPair ),
650
797
ok = end_session_sync (Session ),
651
798
ok = amqp10_client :close_connection (Connection ).
0 commit comments