Skip to content

Commit 03dffc6

Browse files
committed
use a separate rabbitmq-queues grow_to_count <count> command
1 parent 3378ff0 commit 03dffc6

File tree

2 files changed

+285
-0
lines changed

2 files changed

+285
-0
lines changed
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
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.Queues.Commands.GrowToCountCommand do
8+
alias RabbitMQ.CLI.Core.{DocGuide, Validators}
9+
import RabbitMQ.CLI.Core.DataCoercion
10+
11+
@behaviour RabbitMQ.CLI.CommandBehaviour
12+
13+
defp default_opts,
14+
do: %{vhost_pattern: ".*", queue_pattern: ".*", membership: "promotable", errors_only: false}
15+
16+
def switches(),
17+
do: [
18+
vhost_pattern: :string,
19+
queue_pattern: :string,
20+
membership: :string,
21+
errors_only: :boolean
22+
]
23+
24+
def merge_defaults(args, opts) do
25+
{args, Map.merge(default_opts(), opts)}
26+
end
27+
28+
def validate(args, _) when length(args) < 2 do
29+
{:validation_failure, :not_enough_args}
30+
end
31+
32+
def validate(args, _) when length(args) > 2 do
33+
{:validation_failure, :too_many_args}
34+
end
35+
36+
def validate([_, s], _)
37+
when not (s == "all" or
38+
s == "even") do
39+
{:validation_failure, "strategy '#{s}' is not recognised."}
40+
end
41+
42+
def validate([n, _], _)
43+
when (is_integer(n) and n <= 0) do
44+
{:validation_failure, "node count '#{n}' must be greater than 0."}
45+
end
46+
47+
def validate(_, %{membership: m})
48+
when not (m == "promotable" or
49+
m == "non_voter" or
50+
m == "voter") do
51+
{:validation_failure, "voter status '#{m}' is not recognised."}
52+
end
53+
54+
def validate(_, _) do
55+
:ok
56+
end
57+
58+
def validate_execution_environment(args, opts) do
59+
Validators.chain(
60+
[
61+
&Validators.rabbit_is_running/2
62+
],
63+
[args, opts]
64+
)
65+
end
66+
67+
def run([node_count, strategy], %{
68+
node: node_name,
69+
vhost_pattern: vhost_pat,
70+
queue_pattern: queue_pat,
71+
membership: membership,
72+
errors_only: errors_only
73+
}) when is_integer(node_count) do
74+
75+
args = [node_count, vhost_pat, queue_pat, to_atom(strategy)]
76+
77+
args =
78+
case to_atom(membership) do
79+
:promotable -> args
80+
other -> args ++ [other]
81+
end
82+
83+
case :rabbit_misc.rpc_call(node_name, :rabbit_quorum_queue, :grow, args) do
84+
{:error, _} = error ->
85+
error
86+
87+
{:badrpc, _} = error ->
88+
error
89+
90+
results when errors_only ->
91+
for {{:resource, vhost, _kind, name}, {:error, _, _} = res} <- results,
92+
do: [
93+
{:vhost, vhost},
94+
{:name, name},
95+
{:size, format_size(res)},
96+
{:result, format_result(res)}
97+
]
98+
99+
results ->
100+
for {{:resource, vhost, _kind, name}, res} <- results,
101+
do: [
102+
{:vhost, vhost},
103+
{:name, name},
104+
{:size, format_size(res)},
105+
{:result, format_result(res)}
106+
]
107+
end
108+
end
109+
110+
use RabbitMQ.CLI.DefaultOutput
111+
112+
def formatter(), do: RabbitMQ.CLI.Formatters.Table
113+
114+
def usage,
115+
do:
116+
"grow_to_count <node_count> <all | even> [--vhost-pattern <pattern>] [--queue-pattern <pattern>] [--membership <promotable|voter>]"
117+
118+
def usage_additional do
119+
[
120+
["<node_count>", "number of nodes to place replicas on"],
121+
[
122+
"<all | even>",
123+
"add a member for all matching queues or just those whose membership count is an even number"
124+
],
125+
["--queue-pattern <pattern>", "regular expression to match queue names"],
126+
["--vhost-pattern <pattern>", "regular expression to match virtual host names"],
127+
["--membership <promotable|voter>", "add a promotable non-voter (default) or full voter"],
128+
["--errors-only", "only list queues which reported an error"]
129+
]
130+
end
131+
132+
def usage_doc_guides() do
133+
[
134+
DocGuide.quorum_queues()
135+
]
136+
end
137+
138+
def help_section, do: :cluster_management
139+
140+
def description,
141+
do:
142+
"Grows quorum queue clusters by adding member replicas on the specified number of nodes for all matching queues"
143+
144+
def banner([node_count, strategy], _) do
145+
"Growing #{strategy} quorum queues on #{node_count} nodes..."
146+
end
147+
148+
#
149+
# Implementation
150+
#
151+
152+
defp format_size({:ok, size}) do
153+
size
154+
end
155+
156+
defp format_size({:error, _size, :timeout}) do
157+
# the actual size is uncertain here
158+
"?"
159+
end
160+
161+
defp format_size({:error, size, _}) do
162+
size
163+
end
164+
165+
defp format_result({:ok, _size}) do
166+
"ok"
167+
end
168+
169+
defp format_result({:error, _size, :timeout}) do
170+
"error: the operation timed out and may not have been completed"
171+
end
172+
173+
defp format_result({:error, _size, err}) do
174+
to_string(:io_lib.format("error: ~W", [err, 10]))
175+
end
176+
end
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
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.Queues.Commands.GrowToCountCommandTest do
8+
use ExUnit.Case, async: false
9+
import TestHelper
10+
11+
@command RabbitMQ.CLI.Queues.Commands.GrowToCountCommand
12+
13+
setup_all do
14+
RabbitMQ.CLI.Core.Distribution.start()
15+
16+
:ok
17+
end
18+
19+
setup context do
20+
{:ok,
21+
opts: %{
22+
node: get_rabbit_hostname(),
23+
timeout: context[:test_timeout] || 30000,
24+
vhost_pattern: ".*",
25+
queue_pattern: ".*",
26+
membership: "promotable",
27+
errors_only: false
28+
}}
29+
end
30+
31+
test "merge_defaults: defaults to reporting complete results" do
32+
assert @command.merge_defaults([], %{}) ==
33+
{[],
34+
%{
35+
vhost_pattern: ".*",
36+
queue_pattern: ".*",
37+
errors_only: false,
38+
membership: "promotable"
39+
}}
40+
end
41+
42+
test "validate: when no arguments are provided, returns a failure" do
43+
assert @command.validate([], %{}) == {:validation_failure, :not_enough_args}
44+
end
45+
46+
test "validate: when one argument is provided, returns a failure" do
47+
assert @command.validate([5], %{}) == {:validation_failure, :not_enough_args}
48+
end
49+
50+
test "validate: when node count and even are provided, returns a success" do
51+
assert @command.validate([7, "even"], %{}) == :ok
52+
end
53+
54+
test "validate: when node count and all are provided, returns a success" do
55+
assert @command.validate([5, "all"], %{}) == :ok
56+
end
57+
58+
test "validate: when node count and something else is provided, returns a failure" do
59+
assert @command.validate([7, "banana"], %{}) ==
60+
{:validation_failure, "strategy 'banana' is not recognised."}
61+
end
62+
63+
test "validate: when three arguments are provided, returns a failure" do
64+
assert @command.validate([7, "extra-arg", "another-extra-arg"], %{}) ==
65+
{:validation_failure, :too_many_args}
66+
end
67+
68+
test "validate: when membership promotable is provided, returns a success" do
69+
assert @command.validate([5, "all"], %{membership: "promotable"}) == :ok
70+
end
71+
72+
test "validate: when membership voter is provided, returns a success" do
73+
assert @command.validate([7, "all"], %{membership: "voter"}) == :ok
74+
end
75+
76+
test "validate: when membership non_voter is provided, returns a success" do
77+
assert @command.validate([5, "all"], %{membership: "non_voter"}) == :ok
78+
end
79+
80+
test "validate: when wrong membership is provided, returns failure" do
81+
assert @command.validate(["quorum-queue-a", "all"], %{membership: "banana"}) ==
82+
{:validation_failure, "voter status 'banana' is not recognised."}
83+
end
84+
85+
test "validate: when node count greater than zero, returns a success" do
86+
assert @command.validate([7, "all"], %{membership: "voter"}) == :ok
87+
end
88+
89+
test "validate: when node count is zero, returns failure" do
90+
assert @command.validate([0, "all"], %{membership: "voter"}) ==
91+
{:validation_failure, "node count '0' must be greater than 0."}
92+
end
93+
94+
test "validate: when node count is less than zero, returns failure" do
95+
assert @command.validate([-1, "all"], %{membership: "voter"}) ==
96+
{:validation_failure, "node count '-1' must be greater than 0."}
97+
end
98+
99+
@tag test_timeout: 3000
100+
test "run: targeting an unreachable node throws a badrpc when growing to a node count", context do
101+
assert match?(
102+
{:badrpc, _},
103+
@command.run(
104+
[5, "all"],
105+
Map.merge(context[:opts], %{node: :jake@thedog})
106+
)
107+
)
108+
end
109+
end

0 commit comments

Comments
 (0)