Skip to content

Commit c6e2405

Browse files
Merge pull request #14079 from rabbitmq/ik-internal-shovels
Internal/Protected shovels
2 parents 2b83238 + a7c21a1 commit c6e2405

File tree

8 files changed

+229
-58
lines changed

8 files changed

+229
-58
lines changed

deps/rabbitmq_management/priv/www/js/formatters.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1094,3 +1094,15 @@ function fmt_deprecation_phase(phase, deprecation_phases){
10941094
}
10951095
}
10961096
}
1097+
1098+
function fmt_resource(res) {
1099+
return `${res.kind} '${res.name}' in vhost '${res.virtual_host}'`;
1100+
}
1101+
1102+
function fmt_resource_link(res) {
1103+
if (res.kind == "queue") {
1104+
return `${res.kind} '${link_queue(res.virtual_host, res.name, {})}' in vhost '${link_vhost(res.virtual_host)}'`;
1105+
} else if (res.kind == "exchange") {
1106+
return `${res.kind} '${link_exchange(res.virtual_host, res.name, {})}' in vhost '${link_vhost(res.virtual_host)}'`;
1107+
}
1108+
}

deps/rabbitmq_shovel/src/Elixir.RabbitMQ.CLI.Ctl.Commands.DeleteShovelCommand.erl

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
-module('Elixir.RabbitMQ.CLI.Ctl.Commands.DeleteShovelCommand').
99

1010
-include("rabbit_shovel.hrl").
11+
-include_lib("rabbit_common/include/rabbit.hrl").
1112

1213
-behaviour('Elixir.RabbitMQ.CLI.CommandBehaviour').
1314

@@ -31,7 +32,7 @@
3132
%% Callbacks
3233
%%----------------------------------------------------------------------------
3334
usage() ->
34-
<<"delete_shovel [--vhost <vhost>] <name>">>.
35+
<<"delete_shovel [--vhost <vhost>] [--force] <name>">>.
3536

3637
usage_additional() ->
3738
[
@@ -49,20 +50,24 @@ help_section() ->
4950

5051
validate([], _Opts) ->
5152
{validation_failure, not_enough_args};
52-
validate([_, _ | _], _Opts) ->
53+
validate([_, _| _], _Opts) ->
5354
{validation_failure, too_many_args};
5455
validate([_], _Opts) ->
5556
ok.
5657

5758
merge_defaults(A, Opts) ->
58-
{A, maps:merge(#{vhost => <<"/">>}, Opts)}.
59+
{A, maps:merge(#{vhost => <<"/">>,
60+
force => false}, Opts)}.
5961

6062
banner([Name], #{vhost := VHost}) ->
6163
erlang:list_to_binary(io_lib:format("Deleting shovel ~ts in vhost ~ts",
6264
[Name, VHost])).
6365

64-
run([Name], #{node := Node, vhost := VHost}) ->
65-
ActingUser = 'Elixir.RabbitMQ.CLI.Core.Helpers':cli_acting_user(),
66+
run([Name], #{node := Node, vhost := VHost, force := Force}) ->
67+
ActingUser = case Force of
68+
true -> ?INTERNAL_USER;
69+
false -> 'Elixir.RabbitMQ.CLI.Core.Helpers':cli_acting_user()
70+
end,
6671

6772
case rabbit_misc:rpc_call(Node, rabbit_shovel_status, cluster_status_with_nodes, []) of
6873
{badrpc, _} = Error ->
@@ -98,7 +103,7 @@ delete_shovel(ErrMsg, VHost, Name, ActingUser, Opts, Node) ->
98103
end.
99104

100105
switches() ->
101-
[].
106+
[{force, boolean}].
102107

103108
aliases() ->
104109
[].

deps/rabbitmq_shovel/src/rabbit_shovel_parameters.erl

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
src_decl_exchange/4, src_decl_queue/4, src_check_queue/4,
2727
fields_fun/5, props_fun/9]).
2828

29+
-export([is_internal/1, internal_owner/1]).
30+
2931
-import(rabbit_misc, [pget/2, pget/3, pset/3]).
3032

3133
-rabbit_boot_step({?MODULE,
@@ -69,6 +71,17 @@ notify_clear(VHost, <<"shovel">>, Name, _Username) ->
6971

7072
%%----------------------------------------------------------------------------
7173

74+
is_internal(Def) ->
75+
pget(<<"internal">>, Def, false).
76+
77+
internal_owner(Def) ->
78+
case pget(<<"internal_owner">>, Def, undefined) of
79+
undefined -> undefined;
80+
Owner -> rabbit_misc:r(pget(<<"virtual_host">>, Owner),
81+
binary_to_existing_atom(pget(<<"kind">>, Owner)),
82+
pget(<<"name">>, Owner))
83+
end.
84+
7285
validate_src(Def) ->
7386
case protocols(Def) of
7487
{amqp091, _} -> validate_amqp091_src(Def);
@@ -112,7 +125,9 @@ validate_amqp091_dest(Def) ->
112125
end].
113126

114127
shovel_validation() ->
115-
[{<<"reconnect-delay">>, fun rabbit_parameter_validation:number/2,optional},
128+
[{<<"internal">>, fun rabbit_parameter_validation:boolean/2, optional},
129+
{<<"internal_owner">>, fun validate_internal_owner/2, optional},
130+
{<<"reconnect-delay">>, fun rabbit_parameter_validation:number/2,optional},
116131
{<<"ack-mode">>, rabbit_parameter_validation:enum(
117132
['no-ack', 'on-publish', 'on-confirm']), optional},
118133
{<<"src-protocol">>,
@@ -233,6 +248,14 @@ validate_delete_after(Name, Term) ->
233248
{error, "~ts should be a number greater than or equal to 0, \"never\" or \"queue-length\", actually was "
234249
"~tp", [Name, Term]}.
235250

251+
validate_internal_owner(Name, Term0) ->
252+
Term = rabbit_data_coercion:to_proplist(Term0),
253+
254+
rabbit_parameter_validation:proplist(Name, [{<<"name">>, fun rabbit_parameter_validation:binary/2},
255+
{<<"kind">>, rabbit_parameter_validation:enum(
256+
['exchange', 'queue'])},
257+
{<<"virtual_host">>, fun rabbit_parameter_validation:binary/2}], Term).
258+
236259
validate_queue_args(Name, Term0) ->
237260
Term = rabbit_data_coercion:to_proplist(Term0),
238261

deps/rabbitmq_shovel/src/rabbit_shovel_util.erl

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
get_shovel_parameter/1]).
1515

1616
-include_lib("rabbit_common/include/rabbit_framing.hrl").
17+
-include_lib("rabbit_common/include/rabbit.hrl").
1718

1819
-define(ROUTING_HEADER, <<"x-shovelled">>).
1920
-define(TIMESTAMP_HEADER, <<"x-shovelled-timestamp">>).
@@ -45,8 +46,41 @@ delete_shovel(VHost, Name, ActingUser) ->
4546
ok = rabbit_runtime_parameters:clear(VHost, <<"shovel">>, Name, ActingUser),
4647
{error, not_found};
4748
_Obj ->
48-
rabbit_log:info("Will delete runtime parameters of shovel '~ts' in virtual host '~ts'", [Name, VHost]),
49-
ok = rabbit_runtime_parameters:clear(VHost, <<"shovel">>, Name, ActingUser)
49+
ShovelParameters = rabbit_runtime_parameters:value(VHost, <<"shovel">>, Name),
50+
case needs_force_delete(ShovelParameters, ActingUser) of
51+
false ->
52+
rabbit_log:info("Will delete runtime parameters of shovel '~ts' in virtual host '~ts'", [Name, VHost]),
53+
ok = rabbit_runtime_parameters:clear(VHost, <<"shovel">>, Name, ActingUser);
54+
true ->
55+
report_that_protected_shovel_cannot_be_deleted(Name, VHost, ShovelParameters)
56+
end
57+
end.
58+
59+
-spec report_that_protected_shovel_cannot_be_deleted(binary(), binary(), map() | [tuple()]) -> no_return().
60+
report_that_protected_shovel_cannot_be_deleted(Name, VHost, ShovelParameters) ->
61+
case rabbit_shovel_parameters:internal_owner(ShovelParameters) of
62+
undefined ->
63+
rabbit_misc:protocol_error(
64+
resource_locked,
65+
"Cannot delete protected shovel '~ts' in virtual host '~ts'.",
66+
[Name, VHost]);
67+
IOwner ->
68+
rabbit_misc:protocol_error(
69+
resource_locked,
70+
"Cannot delete protected shovel '~ts' in virtual host '~ts'. It was "
71+
"declared as protected, delete it with --force or delete its owner entity instead: ~ts",
72+
[Name, VHost, rabbit_misc:rs(IOwner)])
73+
end.
74+
75+
needs_force_delete(Parameters,ActingUser) ->
76+
case rabbit_shovel_parameters:is_internal(Parameters) of
77+
false ->
78+
false;
79+
true ->
80+
case ActingUser of
81+
?INTERNAL_USER -> false;
82+
_ -> true
83+
end
5084
end.
5185

5286
restart_shovel(VHost, Name) ->

deps/rabbitmq_shovel/test/delete_shovel_command_SUITE.erl

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ groups() ->
2424
[
2525
{non_parallel_tests, [], [
2626
delete_not_found,
27-
delete
27+
delete,
28+
delete_internal,
29+
delete_internal_owner
2830
]},
2931
{cluster_size_2, [], [
3032
clear_param_on_different_node
@@ -73,7 +75,7 @@ end_per_testcase(Testcase, Config) ->
7375
%% -------------------------------------------------------------------
7476
delete_not_found(Config) ->
7577
[A] = rabbit_ct_broker_helpers:get_node_configs(Config, nodename),
76-
Opts = #{node => A, vhost => <<"/">>},
78+
Opts = #{node => A, vhost => <<"/">>, force => false},
7779
{error, _} = ?CMD:run([<<"myshovel">>], Opts).
7880

7981
delete(Config) ->
@@ -82,10 +84,55 @@ delete(Config) ->
8284
<<"myshovel">>, [{<<"src-queue">>, <<"src">>},
8385
{<<"dest-queue">>, <<"dest">>}]),
8486
[A] = rabbit_ct_broker_helpers:get_node_configs(Config, nodename),
85-
Opts = #{node => A, vhost => <<"/">>},
87+
Opts = #{node => A, vhost => <<"/">>, force => false},
8688
ok = ?CMD:run([<<"myshovel">>], Opts),
8789
[] = rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_shovel_status,
8890
status, []).
91+
92+
delete_internal(Config) ->
93+
shovel_test_utils:set_param(
94+
Config,
95+
<<"myshovel">>, [{<<"src-queue">>, <<"src">>},
96+
{<<"internal">>, true},
97+
{<<"dest-queue">>, <<"dest">>}]),
98+
[A] = rabbit_ct_broker_helpers:get_node_configs(Config, nodename),
99+
Opts = #{node => A, vhost => <<"/">>, force => false},
100+
{badrpc,
101+
{'EXIT',
102+
{amqp_error, resource_locked,
103+
"Cannot delete protected shovel 'myshovel' in virtual host '/'.",
104+
none}}} = ?CMD:run([<<"myshovel">>], Opts),
105+
[_] = rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_shovel_status,
106+
status, []),
107+
108+
ForceOpts = #{node => A, vhost => <<"/">>, force => true},
109+
ok = ?CMD:run([<<"myshovel">>], ForceOpts),
110+
[] = rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_shovel_status,
111+
status, []).
112+
113+
delete_internal_owner(Config) ->
114+
shovel_test_utils:set_param(
115+
Config,
116+
<<"myshovel">>, [{<<"src-queue">>, <<"src">>},
117+
{<<"internal">>, true},
118+
{<<"internal_owner">>, [{<<"name">>, <<"src">>},
119+
{<<"kind">>, <<"queue">>},
120+
{<<"virtual_host">>, <<"/">>}]},
121+
{<<"dest-queue">>, <<"dest">>}]),
122+
[A] = rabbit_ct_broker_helpers:get_node_configs(Config, nodename),
123+
Opts = #{node => A, vhost => <<"/">>, force => false},
124+
?assertMatch(
125+
{badrpc, {'EXIT', {amqp_error, resource_locked, _, none}}},
126+
?CMD:run([<<"myshovel">>], Opts)
127+
),
128+
[_] = rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_shovel_status,
129+
status, []),
130+
131+
ForceOpts = #{node => A, vhost => <<"/">>, force => true},
132+
ok = ?CMD:run([<<"myshovel">>], ForceOpts),
133+
[] = rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_shovel_status,
134+
status, []).
135+
89136
clear_param_on_different_node(Config) ->
90137
shovel_test_utils:set_param(
91138
Config,

deps/rabbitmq_shovel_management/priv/www/js/shovel.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,27 @@ function fmt_shovel_endpoint(prefix, shovel) {
206206
return txt;
207207
}
208208

209+
function is_internal_shovel(shovel) {
210+
if (!shovel.hasOwnProperty('internal')) {
211+
return false;
212+
} else {
213+
return shovel['internal'];
214+
}
215+
}
216+
217+
function shovel_has_internal_owner(shovel) {
218+
if (!shovel.hasOwnProperty('internal_owner')) {
219+
return false;
220+
} else {
221+
return true;
222+
}
223+
}
224+
225+
function shovel_internal_owner(shovel) {
226+
return shovel.internal_owner;
227+
}
228+
229+
209230
function fallback_value(shovel, key1, key2) {
210231
var v = shovel.value[key1];
211232
return (v !== undefined ? v : shovel.value[key2]);

deps/rabbitmq_shovel_management/priv/www/js/tmpl/dynamic-shovel.ejs

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,23 @@
4444
</div>
4545
</div>
4646

47-
<div class="section-hidden">
47+
48+
<div class="section-hidden">
4849
<h2>Delete this shovel</h2>
4950
<div class="hider">
50-
<form action="#/shovel-parameters" method="delete" class="confirm">
51-
<input type="hidden" name="component" value="shovel"/>
52-
<input type="hidden" name="vhost" value="<%= fmt_string(shovel.vhost) %>"/>
53-
<input type="hidden" name="name" value="<%= fmt_string(shovel.name) %>"/>
54-
<input type="submit" value="Delete this shovel"/>
55-
</form>
51+
<% if (!is_internal_shovel(shovel.value)) { %>
52+
<form action="#/shovel-parameters" method="delete" class="confirm">
53+
<input type="hidden" name="component" value="shovel"/>
54+
<input type="hidden" name="vhost" value="<%= fmt_string(shovel.vhost) %>"/>
55+
<input type="hidden" name="name" value="<%= fmt_string(shovel.name) %>"/>
56+
<input type="submit" value="Delete this shovel"/>
57+
</form>
58+
<% } else { %>
59+
<% if (shovel_has_internal_owner(shovel.value)) { %>
60+
<span>This shovel is internal and owned by <%= fmt_resource_link(shovel_internal_owner(shovel.value)) %>. Could be deleted only via CLI command with --force.</span>
61+
<% } else { %>
62+
<span>This shovel is internal. Could be deleted only via CLI command with '--force'.</span>
63+
<% } %>
64+
<% } %>
65+
</div>
5666
</div>
57-
</div>

0 commit comments

Comments
 (0)