Skip to content

Commit 4850ab3

Browse files
michaelklishinmergify[bot]
authored andcommitted
Safer federation upstream deletion, closes #16991
If an exchange shared the same (virtual host, name) tuple as a federated queue deleted by the federation plugin, `rabbit_exchange:update_scratch/3` could unintentionally end up being called with the conflicting resource (exchange) virtual host and name, overriding its decorator list. The operation in question does not make sense for federated queues, so let's only perform it for exchanges. Updating ` rabbit_db_exchange:update/2` is another option but it has backwards-compatibility risks and would not fully resolve the issue at hand. (cherry picked from commit 9522020)
1 parent 03e7178 commit 4850ab3

2 files changed

Lines changed: 54 additions & 4 deletions

File tree

deps/rabbitmq_federation_common/src/rabbit_federation_link_sup.erl

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,16 @@ adjust(Sup, LinkMod, XorQ, {upstream, UpstreamName}) ->
5252
_ = [stop(Sup, OldUpstream, XorQ) || OldUpstream <- OldUpstreams],
5353
[start(Sup, LinkMod, NewUpstream, XorQ) || NewUpstream <- NewUpstreams];
5454

55-
adjust(Sup, _LinkMod, XorQ, {clear_upstream, UpstreamName}) ->
55+
adjust(Sup, _LinkMod, X = #exchange{}, {clear_upstream, UpstreamName}) ->
56+
%% The federation scratch only exists for exchanges. Pruning it for a
57+
%% queue passes the queue name to `update_scratch/3`, which resolves it to
58+
%% a same-named exchange (the Khepri path ignores the resource kind) and
59+
%% overwrites that exchange's decorators. See rabbitmq/rabbitmq-server#16991.
5660
ok = rabbit_federation_db:prune_scratch(
57-
name(XorQ), rabbit_federation_upstream:for(XorQ)),
58-
[stop(Sup, Upstream, XorQ) || Upstream <- children(Sup, UpstreamName)];
61+
name(X), rabbit_federation_upstream:for(X)),
62+
[stop(Sup, Upstream, X) || Upstream <- children(Sup, UpstreamName)];
63+
adjust(Sup, _LinkMod, Q, {clear_upstream, UpstreamName}) when ?is_amqqueue(Q) ->
64+
[stop(Sup, Upstream, Q) || Upstream <- children(Sup, UpstreamName)];
5965

6066
adjust(Sup, LinkMod, X = #exchange{name = XName}, {upstream_set, _Set}) ->
6167
_ = adjust(Sup, LinkMod, X, everything),

deps/rabbitmq_queue_federation/test/queue_SUITE.erl

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ federation_mechanics_tests() ->
6060
dynamic_reconfiguration,
6161
federate_unfederate,
6262
dynamic_plugin_stop_start,
63-
supervisor_shutdown_concurrency_safety
63+
supervisor_shutdown_concurrency_safety,
64+
clear_upstream_leaves_same_named_exchange_intact
6465
]}
6566
]}
6667
].
@@ -399,6 +400,49 @@ supervisor_shutdown_concurrency_safety(Config) ->
399400

400401
expect_federation(Ch, UpQ1, DownQ1, ?EXPECT_FEDERATION_TIMEOUT)
401402
end, upstream_downstream(Config) ++ [q(DownQ2, Args)]).
403+
404+
%% Clearing a queue-federation upstream must leave a same-named exchange
405+
%% untouched. See rabbitmq/rabbitmq-server#16991.
406+
clear_upstream_leaves_same_named_exchange_intact(Config) ->
407+
%% The federated queue and a fanout exchange share {vhost, name}.
408+
Name = <<"fed1.downstream">>,
409+
XName = rabbit_misc:r(<<"/">>, exchange, Name),
410+
with_ch(Config,
411+
fun (Ch) ->
412+
#'exchange.declare_ok'{} =
413+
amqp_channel:call(Ch, #'exchange.declare'{exchange = Name,
414+
type = <<"fanout">>,
415+
durable = true}),
416+
#'queue.bind_ok'{} =
417+
amqp_channel:call(Ch, #'queue.bind'{queue = Name,
418+
exchange = Name}),
419+
420+
await_running_federation(Config,
421+
[{Name, <<"upstream">>}],
422+
?EXPECT_FEDERATION_TIMEOUT),
423+
expect_federation(Ch, <<"upstream">>, Name, ?EXPECT_FEDERATION_TIMEOUT),
424+
425+
%% Before clearing, the decorators are a valid {Route, NoRoute} tuple.
426+
{_, _} = exchange_decorators(Config, XName),
427+
428+
clear_upstream(Config, 0, <<"localhost">>),
429+
430+
%% After clearing they must still be a tuple, not a bare [].
431+
{_, _} = exchange_decorators(Config, XName),
432+
433+
%% Publishing to the exchange still routes to the queue rather
434+
%% than crashing the channel with a decorator function_clause.
435+
Payload = <<"after-clear">>,
436+
publish(Ch, Name, <<>>, Payload),
437+
expect(Ch, Name, [Payload])
438+
end, upstream_downstream(Config)).
439+
440+
%% #exchange.decorators
441+
exchange_decorators(Config, XName) ->
442+
{ok, X} = rabbit_ct_broker_helpers:rpc(Config, 0,
443+
rabbit_exchange, lookup, [XName]),
444+
element(11, X).
445+
402446
restart_upstream(Config) ->
403447
[Rabbit, Hare] = rabbit_ct_broker_helpers:get_node_configs(Config,
404448
nodename),

0 commit comments

Comments
 (0)