rabbitmq-server/deps/rabbitmq_shovel/test/amqp10_static_SUITE.erl

304 lines
12 KiB
Erlang

%% This Source Code Form is subject to the terms of the Mozilla Public
%% License, v. 2.0. If a copy of the MPL was not distributed with this
%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
%%
%% Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved.
%%
-module(amqp10_static_SUITE).
-include_lib("common_test/include/ct.hrl").
-include_lib("eunit/include/eunit.hrl").
-include_lib("amqp_client/include/amqp_client.hrl").
-compile(export_all).
-define(EXCHANGE, <<"test_exchange">>).
-define(TO_SHOVEL, <<"to_the_shovel">>).
-define(FROM_SHOVEL, <<"from_the_shovel">>).
-define(UNSHOVELLED, <<"unshovelled">>).
-define(SHOVELLED, <<"shovelled">>).
-define(TIMEOUT, 1000).
all() ->
[
{group, tests}
].
groups() ->
[
{tests, [], [
amqp10_destination_no_ack,
amqp10_destination_on_publish,
amqp10_destination_on_confirm,
amqp10_source_no_ack,
amqp10_source_on_publish,
amqp10_source_on_confirm
]}
].
%% -------------------------------------------------------------------
%% Testsuite setup/teardown.
%% -------------------------------------------------------------------
init_per_suite(Config) ->
{ok, _} = application:ensure_all_started(amqp10_client),
rabbit_ct_helpers:log_environment(),
Config1 = rabbit_ct_helpers:set_config(Config, [
{rmq_nodename_suffix, ?MODULE}
]),
rabbit_ct_helpers:run_setup_steps(Config1,
rabbit_ct_broker_helpers:setup_steps() ++
rabbit_ct_client_helpers:setup_steps() ++
[fun stop_shovel_plugin/1]).
end_per_suite(Config) ->
application:stop(amqp10_client),
rabbit_ct_helpers:run_teardown_steps(Config,
rabbit_ct_client_helpers:teardown_steps() ++
rabbit_ct_broker_helpers:teardown_steps()).
init_per_group(_, Config) ->
Config.
end_per_group(_, Config) ->
Config.
init_per_testcase(Testcase, Config) ->
rabbit_ct_helpers:testcase_started(Config, Testcase).
end_per_testcase(Testcase, Config) ->
rabbit_ct_helpers:testcase_finished(Config, Testcase).
stop_shovel_plugin(Config) ->
ok = rabbit_ct_broker_helpers:rpc(Config, 0,
application, stop, [rabbitmq_shovel]),
Config.
%% -------------------------------------------------------------------
%% Testcases.
%% -------------------------------------------------------------------
amqp10_destination_no_ack(Config) ->
amqp10_destination(Config, no_ack).
amqp10_destination_on_publish(Config) ->
amqp10_destination(Config, on_publish).
amqp10_destination_on_confirm(Config) ->
amqp10_destination(Config, on_confirm).
amqp10_destination(Config, AckMode) ->
TargetQ = <<"a-queue">>,
ok = setup_amqp10_destination_shovel(Config, TargetQ, AckMode),
Hostname = ?config(rmq_hostname, Config),
Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp),
{ok, Conn} = amqp10_client:open_connection(Hostname, Port),
{ok, Sess} = amqp10_client:begin_session(Conn),
{ok, Receiver} = amqp10_client:attach_receiver_link(Sess,
<<"amqp-destination-receiver">>,
TargetQ, settled, unsettled_state),
ok = amqp10_client:flow_link_credit(Receiver, 5, never),
Chan = rabbit_ct_client_helpers:open_channel(Config, 0),
Timestamp = erlang:system_time(millisecond),
Msg = #amqp_msg{payload = <<42>>,
props = #'P_basic'{delivery_mode = 2,
headers = [{<<"header1">>, long, 1},
{<<"header2">>, longstr, <<"h2">>}],
content_encoding = ?UNSHOVELLED,
content_type = ?UNSHOVELLED,
correlation_id = ?UNSHOVELLED,
%% needs to be guest here
user_id = <<"guest">>,
message_id = ?UNSHOVELLED,
reply_to = ?UNSHOVELLED,
timestamp = Timestamp,
type = ?UNSHOVELLED
}},
publish(Chan, Msg, ?EXCHANGE, ?TO_SHOVEL),
receive
{amqp10_msg, Receiver, InMsg} ->
[<<42>>] = amqp10_msg:body(InMsg),
Ts = Timestamp * 1000,
ReplyTo = <<"/queues/", ?UNSHOVELLED/binary>>,
?assertMatch(
#{content_type := ?UNSHOVELLED,
content_encoding := ?UNSHOVELLED,
correlation_id := ?UNSHOVELLED,
user_id := <<"guest">>,
message_id := ?UNSHOVELLED,
reply_to := ReplyTo,
%% Message timestamp is no longer overwritten
creation_time := Ts},
amqp10_msg:properties(InMsg)),
?assertMatch(
#{<<"header1">> := 1,
<<"header2">> := <<"h2">>},
amqp10_msg:application_properties(InMsg)),
?assertMatch(
#{<<"x-basic-type">> := ?UNSHOVELLED,
<<"x-opt-shovel-type">> := <<"static">>,
<<"x-opt-shovel-name">> := <<"test_shovel">>,
<<"x-opt-shovelled-by">> := _,
<<"x-opt-shovelled-timestamp">> := _},
amqp10_msg:message_annotations(InMsg)),
?assertMatch(#{durable := true}, amqp10_msg:headers(InMsg)),
ok
after ?TIMEOUT ->
throw(timeout_waiting_for_deliver1)
end,
[{test_shovel, static, {running, _Info}, _Metrics, _Time}] =
rabbit_ct_broker_helpers:rpc(Config, 0,
rabbit_shovel_status, status, []),
amqp10_client:detach_link(Receiver),
amqp10_client:close_connection(Conn),
rabbit_ct_client_helpers:close_channel(Chan).
amqp10_source_no_ack(Config) ->
amqp10_source(Config, no_ack).
amqp10_source_on_publish(Config) ->
amqp10_source(Config, on_publish).
amqp10_source_on_confirm(Config) ->
amqp10_source(Config, on_confirm).
amqp10_source(Config, AckMode) ->
SourceQ = <<"source-queue">>,
DestQ = <<"dest-queue">>,
ok = setup_amqp10_source_shovel(Config, SourceQ, DestQ, AckMode),
Chan = rabbit_ct_client_helpers:open_channel(Config, 0),
CTag = consume(Chan, DestQ, AckMode =:= no_ack),
Msg = #amqp_msg{payload = <<42>>,
props = #'P_basic'{delivery_mode = 2,
content_type = ?UNSHOVELLED}},
% publish to source
publish(Chan, Msg, <<>>, SourceQ),
receive
{#'basic.deliver'{consumer_tag = CTag, delivery_tag = AckTag},
#amqp_msg{payload = <<42>>,
props = #'P_basic'{%delivery_mode = 2,
%content_type = ?SHOVELLED,
headers = [{<<"x-shovelled">>, _, _},
{<<"x-shovelled-timestamp">>,
long, _}]}}} ->
case AckMode of
no_ack -> ok;
_ -> ok = amqp_channel:call(
Chan, #'basic.ack'{delivery_tag = AckTag})
end
after ?TIMEOUT -> throw(timeout_waiting_for_deliver1)
end,
[{test_shovel, static, {running, _Info}, _Metrics, _Time}] =
rabbit_ct_broker_helpers:rpc(Config, 0,
rabbit_shovel_status, status, []),
rabbit_ct_client_helpers:close_channel(Chan).
setup_amqp10_source_shovel(Config, SourceQueue, DestQueue, AckMode) ->
Hostname = ?config(rmq_hostname, Config),
Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp),
Shovel = [{test_shovel,
[{source,
[{protocol, amqp10},
{uris, [rabbit_misc:format("amqp://~ts:~b",
[Hostname, Port])]},
{source_address, SourceQueue}]
},
{destination,
[{uris, [rabbit_misc:format("amqp://~ts:~b/%2f?heartbeat=5",
[Hostname, Port])]},
{declarations,
[{'queue.declare', [{queue, DestQueue}, auto_delete]}]},
{publish_fields, [{exchange, <<>>},
{routing_key, DestQueue}]},
{publish_properties, [{delivery_mode, 2},
{content_type, ?SHOVELLED}]},
{add_forward_headers, true},
{add_timestamp_header, true}]},
{queue, <<>>},
{ack_mode, AckMode}
]}],
ok = rabbit_ct_broker_helpers:rpc(Config, 0, ?MODULE, setup_shovel,
[Shovel]).
setup_amqp10_destination_shovel(Config, Queue, AckMode) ->
Hostname = ?config(rmq_hostname, Config),
Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp),
Shovel = [{test_shovel,
[{source,
[{uris, [rabbit_misc:format("amqp://~ts:~b/%2f?heartbeat=5",
[Hostname, Port])]},
{declarations,
[{'queue.declare', [exclusive, auto_delete]},
{'exchange.declare', [{exchange, ?EXCHANGE}, auto_delete]},
{'queue.bind', [{queue, <<>>}, {exchange, ?EXCHANGE},
{routing_key, ?TO_SHOVEL}]}]},
{queue, <<>>}]},
{destination,
[{protocol, amqp10},
{uris, [rabbit_misc:format("amqp://~ts:~b",
[Hostname, Port])]},
{add_forward_headers, true},
{add_timestamp_header, true},
{target_address, Queue}]
},
{ack_mode, AckMode}]}],
ok = rabbit_ct_broker_helpers:rpc(Config, 0, ?MODULE, setup_shovel,
[Shovel]).
setup_amqp10_shovel(Config, SourceQueue, DestQueue, AckMode) ->
Hostname = ?config(rmq_hostname, Config),
Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp),
Shovel = [{test_shovel,
[{source,
[{protocol, amqp10},
{uris, [rabbit_misc:format("amqp://~ts:~b",
[Hostname, Port])]},
{source_address, SourceQueue}]},
{destination,
[{protocol, amqp10},
{uris, [rabbit_misc:format("amqp://~ts:~b",
[Hostname, Port])]},
{add_forward_headers, true},
{add_timestamp_header, true},
{target_address, DestQueue}]
},
{ack_mode, AckMode}]}],
ok = rabbit_ct_broker_helpers:rpc(Config, 0, ?MODULE, setup_shovel,
[Shovel]).
setup_shovel(ShovelConfig) ->
_ = application:stop(rabbitmq_shovel),
application:set_env(rabbitmq_shovel, shovels, ShovelConfig, infinity),
ok = application:start(rabbitmq_shovel),
await_running_shovel(test_shovel).
await_running_shovel(Name) ->
case [N || {N, _, {running, _}, _, _}
<- rabbit_shovel_status:status(),
N =:= Name] of
[_] -> ok;
_ -> timer:sleep(100),
await_running_shovel(Name)
end.
consume(Chan, Queue, NoAck) ->
#'basic.consume_ok'{consumer_tag = CTag} =
amqp_channel:subscribe(Chan, #'basic.consume'{queue = Queue,
no_ack = NoAck,
exclusive = false},
self()),
receive
#'basic.consume_ok'{consumer_tag = CTag} -> ok
after ?TIMEOUT -> throw(timeout_waiting_for_consume_ok)
end,
CTag.
publish(Chan, Msg, Exchange, RoutingKey) ->
ok = amqp_channel:call(Chan, #'basic.publish'{exchange = Exchange,
routing_key = RoutingKey},
Msg).