diff options
author | Alex Valiushko <valiushk@amazon.com> | 2022-11-06 15:53:19 -0800 |
---|---|---|
committer | Mergify <37929162+mergify[bot]@users.noreply.github.com> | 2022-11-13 09:18:57 +0000 |
commit | 3c0675d46a4a58fdc2dfe4c7e10167f971901514 (patch) | |
tree | 0a1022c7ef7f4c6a87c9b4da18d7b68fb23a6fb6 | |
parent | 9e452ace8a7a9a96a8b2a5d372bbd7f81bd44aa5 (diff) | |
download | rabbitmq-server-git-3c0675d46a4a58fdc2dfe4c7e10167f971901514.tar.gz |
Add inclusive aliases to ctl info keys
Includes generic ability to provide aliases in other commands.
(cherry picked from commit d70660dac7774bad807f6f548437a3e1d49ef44a)
8 files changed, 198 insertions, 25 deletions
diff --git a/deps/rabbit/docs/rabbitmqctl.8 b/deps/rabbit/docs/rabbitmqctl.8 index 8138819cac..6e5f4d05ca 100644 --- a/deps/rabbit/docs/rabbitmqctl.8 +++ b/deps/rabbit/docs/rabbitmqctl.8 @@ -1394,11 +1394,11 @@ or prefetch count. .It Cm memory Bytes of memory allocated by the runtime for the queue, including stack, heap and internal structures. -.It Cm slave_pids +.It Cm mirror_pids If the queue is mirrored, this lists the IDs of the mirrors (follower replicas). To learn more, see the .Lk https://www.rabbitmq.com/ha.html "RabbitMQ Mirroring guide" -.It Cm synchronised_slave_pids +.It Cm synchronised_mirror_pids If the queue is mirrored, this gives the IDs of the mirrors (follower replicas) which are in sync with the leader replica. To learn more, see the .Lk https://www.rabbitmq.com/ha.html "RabbitMQ Mirroring guide" @@ -1416,6 +1416,8 @@ be shown with a status of (and most other .Ar queueinfoitem will be unavailable). +.It Cm type +Queue type, one of: quorum, stream, classic. .El .Pp If no @@ -1428,9 +1430,45 @@ each queue of the virtual host named .sp .Dl rabbitmqctl list_queues -p my-vhost messages consumers .\" ------------------------------------------------------------------ -.It Cm list_unresponsive_queues Oo Fl -local Oc Oo Fl -queue-timeout Ar milliseconds Oc Oo Ar column ... Oc Op Fl -no-table-headers +.It Cm list_unresponsive_queues Oo Fl -local Oc Oo Fl -queue-timeout Ar milliseconds Oc Oo Ar queueinfoitem ... Oc Op Fl -no-table-headers +.Pp +Tests queues to respond within timeout. Lists those which did not respond. +.Pp +Displayed queues can be filtered by their status or location using one +of the following mutually exclusive options: +.Bl -tag -width Ds +.It Fl -all +List all queues. +.It Fl -local +List only those queues whose leader replica is located on the current +node. +.El .Pp -Tests queues to respond within timeout. Lists those which did not respond +The +.Ar queueinfoitem +parameter is used to indicate which queue information items to include +in the results. +The column order in the results will match the order of the parameters. +.Ar queueinfoitem +can take any value from the list that follows: +.Bl -tag -width Ds +.It Cm name +The name of the queue with non\-ASCII characters escaped as in C. +.It Cm durable +Whether or not the queue survives server restarts. +.It Cm auto_delete +Whether the queue will be deleted automatically when no longer used. +.It Cm arguments +Queue arguments. +.It Cm policy +Effective policy name for the queue. +.It Cm pid +Erlang process identifier of the leader replica. +.It Cm recoverable_mirrors +Erlang process identifiers of the mirror replicas that did respond in time. +.It Cm type +Queue type, one of: quorum, stream, classic. +.El .Pp For example, this command lists only those unresponsive queues whose leader replica is hosted on the target node. diff --git a/deps/rabbitmq_cli/DESIGN.md b/deps/rabbitmq_cli/DESIGN.md index 4179048159..b73d92c96f 100644 --- a/deps/rabbitmq_cli/DESIGN.md +++ b/deps/rabbitmq_cli/DESIGN.md @@ -47,7 +47,7 @@ CLI core consists of several modules implementing command execution process: #### Arguments parsing -Command line arguments are parsed with [OptionParser](https://elixir-lang.org/docs/stable/elixir/OptionParser.html) +Command line arguments are parsed with [OptionParser](https://hexdocs.pm/elixir/OptionParser.html) Parser returns a list of unnamed arguments and a map of options (named arguments) First unnamed argument is a command name. Named arguments can be global or command specific. @@ -412,7 +412,7 @@ and returns a list of strings, that should be printed. format_stream(output_stream :: Enumerable.t, options :: Map.t) :: Enumerable.t Format a stream of return values. This function uses elixir -Stream [https://elixir-lang.org/docs/stable/elixir/Stream.html] abstraction +Stream [https://hexdocs.pm/elixir/Stream.html] abstraction to define processing of continuous data, so the CLI can output data in realtime. Used in `list_*` commands, that emit data asynchronously. diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_channels_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_channels_command.ex index 218b0171d5..2e4222e068 100644 --- a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_channels_command.ex +++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_channels_command.ex @@ -43,13 +43,14 @@ defmodule RabbitMQ.CLI.Ctl.Commands.ListChannelsCommand do def run([_ | _] = args, %{node: node_name, timeout: timeout}) do info_keys = InfoKeys.prepare_info_keys(args) + broker_keys = InfoKeys.broker_keys(info_keys) Helpers.with_nodes_in_cluster(node_name, fn nodes -> RpcStream.receive_list_items( node_name, :rabbit_channel, :emit_info_all, - [nodes, info_keys], + [nodes, broker_keys], timeout, info_keys, Kernel.length(nodes) diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_connections_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_connections_command.ex index a4ee6b568f..02353dda5b 100644 --- a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_connections_command.ex +++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_connections_command.ex @@ -42,13 +42,14 @@ defmodule RabbitMQ.CLI.Ctl.Commands.ListConnectionsCommand do def run([_ | _] = args, %{node: node_name, timeout: timeout}) do info_keys = InfoKeys.prepare_info_keys(args) + broker_keys = InfoKeys.broker_keys(info_keys) Helpers.with_nodes_in_cluster(node_name, fn nodes -> RpcStream.receive_list_items( node_name, :rabbit_networking, :emit_connection_info_all, - [nodes, info_keys], + [nodes, broker_keys], timeout, info_keys, Kernel.length(nodes) diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_queues_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_queues_command.ex index 2dc44d87d0..f2ae914728 100644 --- a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_queues_command.ex +++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_queues_command.ex @@ -23,7 +23,12 @@ defmodule RabbitMQ.CLI.Ctl.Commands.ListQueuesCommand do head_message_timestamp disk_reads disk_writes consumers consumer_utilisation consumer_capacity memory slave_pids synchronised_slave_pids state type - leader members online)a + leader members online + mirror_pids synchronised_mirror_pids)a + @info_key_aliases [ + {:mirror_pids, :slave_pids}, + {:synchronised_mirror_pids, :synchronised_slave_pids} + ] def description(), do: "Lists queues and their properties" @@ -61,7 +66,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.ListQueuesCommand do end def validate(args, _opts) do - case InfoKeys.validate_info_keys(args, @info_keys) do + case InfoKeys.validate_info_keys(args, @info_keys, @info_key_aliases) do {:ok, _} -> :ok err -> err end @@ -85,12 +90,13 @@ defmodule RabbitMQ.CLI.Ctl.Commands.ListQueuesCommand do other -> other end - info_keys = InfoKeys.prepare_info_keys(args) + info_keys = InfoKeys.prepare_info_keys(args, @info_key_aliases) + broker_keys = InfoKeys.broker_keys(info_keys) Helpers.with_nodes_in_cluster(node_name, fn nodes -> - offline_mfa = {:rabbit_amqqueue, :emit_info_down, [vhost, info_keys]} - local_mfa = {:rabbit_amqqueue, :emit_info_local, [vhost, info_keys]} - online_mfa = {:rabbit_amqqueue, :emit_info_all, [nodes, vhost, info_keys]} + offline_mfa = {:rabbit_amqqueue, :emit_info_down, [vhost, broker_keys]} + local_mfa = {:rabbit_amqqueue, :emit_info_local, [vhost, broker_keys]} + online_mfa = {:rabbit_amqqueue, :emit_info_all, [nodes, vhost, broker_keys]} {chunks, mfas} = case {local_opt, offline, online} do diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_unresponsive_queues_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_unresponsive_queues_command.ex index 9fa818f195..0101d462f3 100644 --- a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_unresponsive_queues_command.ex +++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/list_unresponsive_queues_command.ex @@ -14,7 +14,9 @@ defmodule RabbitMQ.CLI.Ctl.Commands.ListUnresponsiveQueuesCommand do @behaviour RabbitMQ.CLI.CommandBehaviour @info_keys ~w(name durable auto_delete - arguments pid recoverable_slaves)a + arguments pid recoverable_slaves + recoverable_mirrors)a + @info_key_aliases [recoverable_mirrors: :recoverable_slaves] def info_keys(), do: @info_keys @@ -39,7 +41,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.ListUnresponsiveQueuesCommand do end def validate(args, _opts) do - case InfoKeys.validate_info_keys(args, @info_keys) do + case InfoKeys.validate_info_keys(args, @info_keys, @info_key_aliases) do {:ok, _} -> :ok err -> err end @@ -54,12 +56,15 @@ defmodule RabbitMQ.CLI.Ctl.Commands.ListUnresponsiveQueuesCommand do queue_timeout: qtimeout, local: local_opt }) do - info_keys = InfoKeys.prepare_info_keys(args) + info_keys = InfoKeys.prepare_info_keys(args, @info_key_aliases) + broker_keys = InfoKeys.broker_keys(info_keys) queue_timeout = qtimeout * 1000 Helpers.with_nodes_in_cluster(node_name, fn nodes -> - local_mfa = {:rabbit_amqqueue, :emit_unresponsive_local, [vhost, info_keys, queue_timeout]} - all_mfa = {:rabbit_amqqueue, :emit_unresponsive, [nodes, vhost, info_keys, queue_timeout]} + local_mfa = + {:rabbit_amqqueue, :emit_unresponsive_local, [vhost, broker_keys, queue_timeout]} + + all_mfa = {:rabbit_amqqueue, :emit_unresponsive, [nodes, vhost, broker_keys, queue_timeout]} {chunks, mfas} = case local_opt do diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/info_keys.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/info_keys.ex index c4eb806f7c..a52fd66cd0 100644 --- a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/info_keys.ex +++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/info_keys.ex @@ -8,8 +8,19 @@ defmodule RabbitMQ.CLI.Ctl.InfoKeys do import RabbitCommon.Records alias RabbitMQ.CLI.Core.DataCoercion + # internal to requested keys + @type info_keys :: Erlang.proplist() + # requested to internal keys + @type aliases :: keyword(atom) + def validate_info_keys(args, valid_keys) do - info_keys = prepare_info_keys(args) + validate_info_keys(args, valid_keys, []) + end + + @spec validate_info_keys([charlist], [charlist], aliases) :: + {:ok, info_keys} | {:validation_failure, any} + def validate_info_keys(args, valid_keys, aliases) do + info_keys = prepare_info_keys(args, aliases) case invalid_info_keys(info_keys, Enum.map(valid_keys, &DataCoercion.to_atom/1)) do [_ | _] = bad_info_keys -> @@ -21,35 +32,81 @@ defmodule RabbitMQ.CLI.Ctl.InfoKeys do end def prepare_info_keys(args) do + prepare_info_keys(args, []) + end + + @spec prepare_info_keys([charlist], aliases) :: info_keys + def prepare_info_keys(args, aliases) do args |> Enum.flat_map(fn arg -> String.split(arg, ",", trim: true) end) |> Enum.map(fn s -> String.replace(s, ",", "") end) |> Enum.map(&String.trim/1) |> Enum.map(&String.to_atom/1) + |> Enum.map(fn k -> + case Keyword.get(aliases, k) do + nil -> k + v -> {v, k} + end + end) |> Enum.uniq() + |> :proplists.compact() + end + + def broker_keys(info_keys) do + Enum.map( + info_keys, + fn + {k, _} -> k + k -> k + end + ) end def with_valid_info_keys(args, valid_keys, fun) do - case validate_info_keys(args, valid_keys) do - {:ok, info_keys} -> fun.(info_keys) + with_valid_info_keys(args, valid_keys, [], fun) + end + + @spec with_valid_info_keys([charlist], [charlist], aliases, fun([atom])) :: any + def with_valid_info_keys(args, valid_keys, aliases, fun) do + case validate_info_keys(args, valid_keys, aliases) do + {:ok, info_keys} -> fun.(:proplists.get_keys(info_keys)) err -> err end end + @spec invalid_info_keys(info_keys, [atom]) :: [atom] defp invalid_info_keys(info_keys, valid_keys) do - MapSet.new(info_keys) + info_keys + |> :proplists.get_keys() + |> MapSet.new() |> MapSet.difference(MapSet.new(valid_keys)) |> MapSet.to_list() + |> Enum.map(fn k -> + case :proplists.get_value(k, info_keys, k) do + true -> k + v -> v + end + end) end + @spec info_for_keys(keyword, info_keys) :: keyword + def info_for_keys(item, []) do item end def info_for_keys([{_, _} | _] = item, info_keys) do item - |> Enum.filter(fn {k, _} -> Enum.member?(info_keys, k) end) - |> Enum.map(fn {k, v} -> {k, format_info_item(v)} end) + |> Enum.filter(fn {k, _} -> :proplists.is_defined(k, info_keys) end) + |> Enum.map(fn {k, v} -> + original = + case :proplists.get_value(k, info_keys) do + true -> k + v -> v + end + + {original, format_info_item(v)} + end) end defp format_info_item(resource(name: name)) do diff --git a/deps/rabbitmq_cli/test/ctl/info_keys_test.exs b/deps/rabbitmq_cli/test/ctl/info_keys_test.exs new file mode 100644 index 0000000000..73a5493360 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/info_keys_test.exs @@ -0,0 +1,65 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule InfoKeysTest do + use ExUnit.Case + + import RabbitMQ.CLI.Ctl.InfoKeys + + test "prepare translates aliases" do + assert prepare_info_keys(["apple"], apple: :banana) == [banana: :apple] + end + + test "prepare works without aliases" do + assert prepare_info_keys(["apple"], []) == [:apple] + assert prepare_info_keys(["apple"]) == [:apple] + end + + test "validate translates aliases" do + assert validate_info_keys(["apple"], ["banana"], apple: :banana) == + {:ok, [banana: :apple]} + end + + test "validate works without aliases" do + assert validate_info_keys(["apple"], ["apple"], []) == {:ok, [:apple]} + assert validate_info_keys(["apple"], ["apple"]) == {:ok, [:apple]} + end + + test "with_valid translates aliases" do + assert with_valid_info_keys(["apple"], ["banana"], [apple: :banana], fn v -> v end) == + [:banana] + end + + test "with_valid works without aliases" do + assert with_valid_info_keys(["apple"], ["apple"], [], fn v -> v end) == [:apple] + assert with_valid_info_keys(["apple"], ["apple"], fn v -> v end) == [:apple] + end + + test "broker_keys preserves order" do + keys = ["a", "b", "c"] + broker_keys = prepare_info_keys(keys) |> broker_keys() + assert broker_keys == [:a, :b, :c] + end + + test "info_keys preserves requested key names" do + aliases = [apple: :banana] + broker_response = [banana: "bonono", carrot: "corrot"] + + keysA = ["banana", "carrot"] + keysB = ["apple", "carrot"] + + normalizedA = prepare_info_keys(keysA, aliases) + normalizedB = prepare_info_keys(keysB, aliases) + + assert :proplists.get_keys(normalizedA) == :proplists.get_keys(normalizedB) + + returnA = info_for_keys(broker_response, normalizedA) + returnB = info_for_keys(broker_response, normalizedB) + + assert broker_keys(returnA) == Enum.map(keysA, &String.to_atom/1) + assert broker_keys(returnB) == Enum.map(keysB, &String.to_atom/1) + end +end |