Merge pull request #6864 from rabbitmq/rabbitmq-server-6853

diagnostics check_port_connectivity: support --address
This commit is contained in:
Michael Klishin 2023-01-16 18:54:52 -06:00 committed by GitHub
commit 9b0d2d3e60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 57 additions and 23 deletions

View File

@ -14,7 +14,7 @@ defmodule RabbitMQ.CLI.Diagnostics.Commands.CheckPortConnectivityCommand do
""" """
import RabbitMQ.CLI.Diagnostics.Helpers, import RabbitMQ.CLI.Diagnostics.Helpers,
only: [check_listener_connectivity: 3] only: [check_listener_connectivity: 4]
import RabbitMQ.CLI.Core.Platform, only: [line_separator: 0] import RabbitMQ.CLI.Core.Platform, only: [line_separator: 0]
import RabbitMQ.CLI.Core.Listeners import RabbitMQ.CLI.Core.Listeners
@ -23,7 +23,8 @@ defmodule RabbitMQ.CLI.Diagnostics.Commands.CheckPortConnectivityCommand do
@default_timeout 30_000 @default_timeout 30_000
use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout def switches(), do: [timeout: :integer, address: :string]
def aliases(), do: [t: :timeout, a: :address]
def merge_defaults(args, opts) do def merge_defaults(args, opts) do
timeout = timeout =
@ -33,13 +34,14 @@ defmodule RabbitMQ.CLI.Diagnostics.Commands.CheckPortConnectivityCommand do
other -> other other -> other
end end
{args, Map.merge(opts, %{timeout: timeout})} opts1 = Map.merge(%{address: nil}, opts)
{args, Map.merge(opts1, %{timeout: timeout})}
end end
use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
use RabbitMQ.CLI.Core.RequiresRabbitAppRunning use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
def run([], %{node: node_name, timeout: timeout}) do def run([], %{node: node_name, address: target_ip, timeout: timeout}) do
case :rabbit_misc.rpc_call(node_name, :rabbit_networking, :active_listeners, [], timeout) do case :rabbit_misc.rpc_call(node_name, :rabbit_networking, :active_listeners, [], timeout) do
{:error, _} = err -> {:error, _} = err ->
err err
@ -52,7 +54,7 @@ defmodule RabbitMQ.CLI.Diagnostics.Commands.CheckPortConnectivityCommand do
case locals do case locals do
[] -> {true, locals} [] -> {true, locals}
_ -> check_connectivity_of(locals, node_name, timeout) _ -> check_connectivity_of(locals, node_name, target_ip, timeout)
end end
other -> other ->
@ -64,7 +66,7 @@ defmodule RabbitMQ.CLI.Diagnostics.Commands.CheckPortConnectivityCommand do
{:ok, %{"result" => "ok", "node" => node_name, "listeners" => listener_maps(listeners)}} {:ok, %{"result" => "ok", "node" => node_name, "listeners" => listener_maps(listeners)}}
end end
def output({true, listeners}, %{node: node_name}) do def output({true, listeners}, %{node: node_name, address: nil}) do
ports = ports =
listeners listeners
|> listener_maps |> listener_maps
@ -72,7 +74,20 @@ defmodule RabbitMQ.CLI.Diagnostics.Commands.CheckPortConnectivityCommand do
|> Enum.sort() |> Enum.sort()
|> Enum.join(", ") |> Enum.join(", ")
{:ok, "Successfully connected to ports #{ports} on node #{node_name}."} {:ok,
"Successfully connected to ports #{ports} on node #{node_name} (using node hostname resolution)"}
end
def output({true, listeners}, %{node: node_name, address: target_ip}) do
ports =
listeners
|> listener_maps
|> Enum.map(fn %{port: p} -> p end)
|> Enum.sort()
|> Enum.join(", ")
{:ok,
"Successfully connected to ports #{ports} on node #{node_name} (using #{target_ip} for target IP address)"}
end end
def output({false, failures}, %{formatter: "json", node: node_name}) do def output({false, failures}, %{formatter: "json", node: node_name}) do
@ -93,24 +108,28 @@ defmodule RabbitMQ.CLI.Diagnostics.Commands.CheckPortConnectivityCommand do
def help_section(), do: :observability_and_health_checks def help_section(), do: :observability_and_health_checks
def usage, do: "check_port_connectivity" def usage, do: "check_port_connectivity [--address <target address>]"
def banner([], %{node: node_name}) do def banner([], %{node: node_name, address: nil}) do
"Testing TCP connections to all active listeners on node #{node_name} ..." "Testing TCP connections to all active listeners on node #{node_name} using hostname resolution ..."
end
def banner([], %{node: node_name, address: target_ip}) do
"Testing TCP connections to all active listeners on node #{node_name} using #{target_ip} for node IP address ..."
end end
# #
# Implementation # Implementation
# #
defp check_connectivity_of(listeners, node_name, timeout) do defp check_connectivity_of(listeners, node_name, address, timeout) do
# per listener timeout # per listener timeout
t = Kernel.trunc(timeout / (length(listeners) + 1)) t = Kernel.trunc(timeout / (length(listeners) + 1))
failures = failures =
Enum.reject( Enum.reject(
listeners, listeners,
fn l -> check_listener_connectivity(listener_map(l), node_name, t) end fn l -> check_listener_connectivity(listener_map(l), node_name, address, t) end
) )
case failures do case failures do

View File

@ -5,20 +5,33 @@
## Copyright (c) 2007-2023 VMware, Inc. or its affiliates. All rights reserved. ## Copyright (c) 2007-2023 VMware, Inc. or its affiliates. All rights reserved.
defmodule RabbitMQ.CLI.Diagnostics.Helpers do defmodule RabbitMQ.CLI.Diagnostics.Helpers do
def test_connection(hostname, port, timeout) do def test_connection(hostname_or_ip, port, timeout) do
case :gen_tcp.connect(hostname, port, [], timeout) do hostname_as_list = :rabbit_data_coercion.to_list(hostname_or_ip)
{:error, _} -> :gen_tcp.connect(hostname, port, [:inet6], timeout)
case :gen_tcp.connect(hostname_as_list, port, [], timeout) do
{:error, _} -> :gen_tcp.connect(hostname_as_list, port, [:inet6], timeout)
r -> r r -> r
end end
end end
def check_port_connectivity(port, node_name, timeout) do def check_port_connectivity(port, node_name, timeout) do
check_port_connectivity(port, node_name, nil, timeout)
end
def check_port_connectivity(port, node_name, nil, timeout) do
regex = Regex.recompile!(~r/^(.+)@/) regex = Regex.recompile!(~r/^(.+)@/)
hostname = Regex.replace(regex, to_string(node_name), "") |> to_charlist hostname = Regex.replace(regex, to_string(node_name), "") |> to_charlist
check_port_connectivity(port, node_name, hostname, timeout)
end
def check_port_connectivity(port, node_name, hostname_or_ip, timeout) do
try do try do
case test_connection(hostname, port, timeout) do IO.puts("Will connect to #{hostname_or_ip}:#{port}")
{:error, _} ->
case test_connection(hostname_or_ip, port, timeout) do
{:error, err} ->
IO.puts("Error connecting to #{hostname_or_ip}:#{port}: #{err}")
false false
{:ok, port} -> {:ok, port} ->
@ -33,7 +46,7 @@ defmodule RabbitMQ.CLI.Diagnostics.Helpers do
end end
end end
def check_listener_connectivity(%{port: port}, node_name, timeout) do def check_listener_connectivity(%{port: port}, node_name, target_ip, timeout) do
check_port_connectivity(port, node_name, timeout) check_port_connectivity(port, node_name, target_ip, timeout)
end end
end end

View File

@ -20,12 +20,13 @@ defmodule CheckPortConnectivityCommandTest do
{:ok, {:ok,
opts: %{ opts: %{
node: get_rabbit_hostname(), node: get_rabbit_hostname(),
address: nil,
timeout: context[:test_timeout] || 30000 timeout: context[:test_timeout] || 30000
}} }}
end end
test "merge_defaults: provides a default timeout" do test "merge_defaults: provides a default timeout" do
assert @command.merge_defaults([], %{}) == {[], %{timeout: 30000}} assert @command.merge_defaults([], %{}) == {[], %{address: nil, timeout: 30000}}
end end
test "validate: treats positional arguments as a failure" do test "validate: treats positional arguments as a failure" do
@ -40,11 +41,12 @@ defmodule CheckPortConnectivityCommandTest do
test "run: targeting an unreachable node throws a badrpc", context do test "run: targeting an unreachable node throws a badrpc", context do
assert match?( assert match?(
{:badrpc, _}, {:badrpc, _},
@command.run([], Map.merge(context[:opts], %{node: :jake@thedog})) @command.run([], Map.merge(context[:opts], %{node: :jake@thedog, address: nil}))
) )
end end
test "run: tries to connect to every inferred active listener", context do test "run: without --address tries to connect to every inferred active listener using hostname resolution",
context do
assert match?({true, _}, @command.run([], context[:opts])) assert match?({true, _}, @command.run([], context[:opts]))
end end
@ -53,7 +55,7 @@ defmodule CheckPortConnectivityCommandTest do
end end
# note: it's run/2 that filters out non-local alarms # note: it's run/2 that filters out non-local alarms
test "output: when target node has a local alarm in effect, returns a failure", context do test "output: when check failed to connect to a port, returns a failure", context do
failure = failure =
{:listener, :rabbit@mercurio, :lolz, 'mercurio', {0, 0, 0, 0, 0, 0, 0, 0}, 7_761_613, {:listener, :rabbit@mercurio, :lolz, 'mercurio', {0, 0, 0, 0, 0, 0, 0, 0}, 7_761_613,
[backlog: 128, nodelay: true]} [backlog: 128, nodelay: true]}