Skip to content

Commit f9a1ed9

Browse files
Merge pull request #13528 from rabbitmq/ik-dpc-cli-force-queue-deletion
By @ikavgo: add a --force option to 'rabbitmqctl delete_queue'
2 parents f1396b5 + 4bb21d7 commit f9a1ed9

File tree

4 files changed

+103
-22
lines changed

4 files changed

+103
-22
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
## This Source Code Form is subject to the terms of the Mozilla Public
2+
## License, v. 2.0. If a copy of the MPL was not distributed with this
3+
## file, You can obtain one at https://mozilla.org/MPL/2.0/.
4+
##
5+
## Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved.
6+
7+
defmodule RabbitMQ.CLI.Core.Users do
8+
# Defined here to not drag in rabbit.hrl and Erlang compilation in an Elixir
9+
# sub-project
10+
@internal_user "rmq-internal"
11+
@cli_user "cli-user"
12+
13+
def internal_user do
14+
@internal_user
15+
end
16+
17+
def cli_user do
18+
@cli_user
19+
end
20+
end

deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/delete_queue_command.ex

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,17 @@
55
## Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved.
66

77
defmodule RabbitMQ.CLI.Ctl.Commands.DeleteQueueCommand do
8-
alias RabbitMQ.CLI.Core.DocGuide
8+
alias RabbitMQ.CLI.Core.{DocGuide, Users}
99

1010
@behaviour RabbitMQ.CLI.CommandBehaviour
1111

12-
def switches(), do: [if_empty: :boolean, if_unused: :boolean, timeout: :integer]
12+
def switches(), do: [if_empty: :boolean, if_unused: :boolean, force: :boolean, timeout: :integer]
1313
def aliases(), do: [e: :if_empty, u: :if_unused, t: :timeout]
1414

1515
def merge_defaults(args, opts) do
1616
{
1717
args,
18-
Map.merge(%{if_empty: false, if_unused: false, vhost: "/"}, opts)
18+
Map.merge(%{if_empty: false, if_unused: false, force: false, vhost: "/"}, opts)
1919
}
2020
end
2121

@@ -46,37 +46,49 @@ defmodule RabbitMQ.CLI.Ctl.Commands.DeleteQueueCommand do
4646
vhost: vhost,
4747
if_empty: if_empty,
4848
if_unused: if_unused,
49+
force: force,
4950
timeout: timeout
5051
}) do
5152
## Generate queue resource name from queue name and vhost
5253
queue_resource = :rabbit_misc.r(vhost, :queue, qname)
54+
user = if force, do: Users.internal_user, else: Users.cli_user
5355
## Lookup a queue on broker node using resource name
5456
case :rabbit_misc.rpc_call(node, :rabbit_amqqueue, :lookup, [queue_resource]) do
5557
{:ok, queue} ->
5658
## Delete queue
57-
:rabbit_misc.rpc_call(
58-
node,
59-
:rabbit_amqqueue,
60-
:delete_with,
61-
[queue, if_unused, if_empty, "cli_user"],
62-
timeout
63-
)
59+
case :rabbit_misc.rpc_call(node,
60+
:rabbit_amqqueue,
61+
:delete_with,
62+
[queue, if_unused, if_empty, user],
63+
timeout
64+
) do
65+
{:ok, _} = ok -> ok
66+
67+
{:badrpc, {:EXIT, {:amqp_error, :resource_locked, _, :none}}} ->
68+
{:error, :protected}
69+
70+
other_error -> other_error
71+
end
6472

6573
{:error, _} = error ->
6674
error
6775
end
6876
end
6977

78+
def output({:error, :protected}, _options) do
79+
{:error, RabbitMQ.CLI.Core.ExitCodes.exit_usage(), "The queue is locked or protected from deletion"}
80+
end
81+
7082
def output({:error, :not_found}, _options) do
71-
{:error, RabbitMQ.CLI.Core.ExitCodes.exit_usage(), "Queue not found"}
83+
{:error, RabbitMQ.CLI.Core.ExitCodes.exit_usage(), "No such queue was found"}
7284
end
7385

7486
def output({:error, :not_empty}, _options) do
75-
{:error, RabbitMQ.CLI.Core.ExitCodes.exit_usage(), "Queue is not empty"}
87+
{:error, RabbitMQ.CLI.Core.ExitCodes.exit_usage(), "The queue is not empty"}
7688
end
7789

7890
def output({:error, :in_use}, _options) do
79-
{:error, RabbitMQ.CLI.Core.ExitCodes.exit_usage(), "Queue is in use"}
91+
{:error, RabbitMQ.CLI.Core.ExitCodes.exit_usage(), "The queue is in use"}
8092
end
8193

8294
def output({:ok, qlen}, _options) do
@@ -103,14 +115,15 @@ defmodule RabbitMQ.CLI.Ctl.Commands.DeleteQueueCommand do
103115
Enum.join(Enum.concat([if_empty_str, if_unused_str]), "and ") <> "..."
104116
end
105117

106-
def usage(), do: "delete_queue [--vhost <vhost>] <queue_name> [--if-empty|-e] [--if-unused|-u]"
118+
def usage(), do: "delete_queue [--vhost <vhost>] <queue_name> [--if-empty|-e] [--if-unused|-u] [--force]"
107119

108120
def usage_additional() do
109121
[
110122
["--vhost", "Virtual host name"],
111123
["<queue_name>", "name of the queue to delete"],
112124
["--if-empty", "delete the queue if it is empty (has no messages ready for delivery)"],
113-
["--if-unused", "delete the queue only if it has no consumers"]
125+
["--if-unused", "delete the queue only if it has no consumers"],
126+
["--force", "delete the queue even if it is protected"]
114127
]
115128
end
116129

deps/rabbitmq_cli/test/ctl/delete_queue_command_test.exs

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,17 @@ defmodule DeleteQueueCommandTest do
2525
vhost: @vhost,
2626
timeout: context[:test_timeout],
2727
if_empty: false,
28-
if_unused: false
28+
if_unused: false,
29+
force: false
2930
}}
3031
end
3132

3233
test "merge_defaults: defaults can be overridden" do
3334
assert @command.merge_defaults([], %{}) ==
34-
{[], %{vhost: "/", if_empty: false, if_unused: false}}
35+
{[], %{vhost: "/", if_empty: false, if_unused: false, force: false}}
3536

3637
assert @command.merge_defaults([], %{vhost: "non_default", if_empty: true}) ==
37-
{[], %{vhost: "non_default", if_empty: true, if_unused: false}}
38+
{[], %{vhost: "non_default", if_empty: true, if_unused: false, force: false}}
3839
end
3940

4041
test "validate: providing no queue name fails validation", context do
@@ -76,6 +77,25 @@ defmodule DeleteQueueCommandTest do
7677
{:error, :not_found} = lookup_queue(q, @vhost)
7778
end
7879

80+
@tag test_timeout: 30000
81+
test "run: protected queue can be deleted only with --force", context do
82+
add_vhost(@vhost)
83+
set_permissions(@user, @vhost, [".*", ".*", ".*"])
84+
on_exit(context, fn -> delete_vhost(@vhost) end)
85+
86+
q = "foo"
87+
n = 20
88+
89+
declare_internal_queue(q, @vhost)
90+
publish_messages(@vhost, q, n)
91+
92+
assert @command.run([q], context[:opts]) == {:error, :protected}
93+
{:ok, _queue} = lookup_queue(q, @vhost)
94+
95+
assert @command.run([q], %{context[:opts] | force: true}) == {:ok, n}
96+
{:error, :not_found} = lookup_queue(q, @vhost)
97+
end
98+
7999
@tag test_timeout: 30000
80100
test "run: request to an existing crashed queue on active node succeeds", context do
81101
add_vhost(@vhost)
@@ -135,7 +155,7 @@ defmodule DeleteQueueCommandTest do
135155

136156
test "defaults to vhost /" do
137157
assert @command.merge_defaults(["foo"], %{bar: "baz"}) ==
138-
{["foo"], %{bar: "baz", vhost: "/", if_unused: false, if_empty: false}}
158+
{["foo"], %{bar: "baz", vhost: "/", if_unused: false, if_empty: false, force: false}}
139159
end
140160

141161
test "validate: with extra arguments returns an arg count error" do
@@ -152,13 +172,13 @@ defmodule DeleteQueueCommandTest do
152172
end
153173

154174
test "banner informs that vhost's queue is deleted" do
155-
assert @command.banner(["my-q"], %{vhost: "/foo", if_empty: false, if_unused: false}) ==
175+
assert @command.banner(["my-q"], %{vhost: "/foo", if_empty: false, if_unused: false, force: false}) ==
156176
"Deleting queue 'my-q' on vhost '/foo' ..."
157177

158-
assert @command.banner(["my-q"], %{vhost: "/foo", if_empty: true, if_unused: false}) ==
178+
assert @command.banner(["my-q"], %{vhost: "/foo", if_empty: true, if_unused: false, force: false}) ==
159179
"Deleting queue 'my-q' on vhost '/foo' if queue is empty ..."
160180

161-
assert @command.banner(["my-q"], %{vhost: "/foo", if_empty: true, if_unused: true}) ==
181+
assert @command.banner(["my-q"], %{vhost: "/foo", if_empty: true, if_unused: true, force: false}) ==
162182
"Deleting queue 'my-q' on vhost '/foo' if queue is empty and if queue is unused ..."
163183
end
164184
end

deps/rabbitmq_cli/test/test_helper.exs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,34 @@ defmodule TestHelper do
302302
])
303303
end
304304

305+
def declare_internal_queue(
306+
name,
307+
vhost,
308+
durable \\ false,
309+
auto_delete \\ false,
310+
args \\ [],
311+
owner \\ :none
312+
) do
313+
queue_name = :rabbit_misc.r(vhost, :queue, name)
314+
315+
amqqueue = :amqqueue.new(
316+
queue_name,
317+
:none,
318+
durable,
319+
auto_delete,
320+
owner,
321+
args,
322+
vhost,
323+
%{})
324+
325+
internal_amqqueue = :amqqueue.make_internal(amqqueue)
326+
327+
:rpc.call(get_rabbit_hostname(), :rabbit_queue_type, :declare, [
328+
internal_amqqueue,
329+
get_rabbit_hostname()
330+
])
331+
end
332+
305333
def declare_stream(name, vhost) do
306334
declare_queue(name, vhost, true, false, [{"x-queue-type", :longstr, "stream"}])
307335
end

0 commit comments

Comments
 (0)