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,
only: [check_listener_connectivity: 3]
only: [check_listener_connectivity: 4]
import RabbitMQ.CLI.Core.Platform, only: [line_separator: 0]
import RabbitMQ.CLI.Core.Listeners
@ -23,7 +23,8 @@ defmodule RabbitMQ.CLI.Diagnostics.Commands.CheckPortConnectivityCommand do
@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
timeout =
@ -33,13 +34,14 @@ defmodule RabbitMQ.CLI.Diagnostics.Commands.CheckPortConnectivityCommand do
other -> other
end
{args, Map.merge(opts, %{timeout: timeout})}
opts1 = Map.merge(%{address: nil}, opts)
{args, Map.merge(opts1, %{timeout: timeout})}
end
use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
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
{:error, _} = err ->
err
@ -52,7 +54,7 @@ defmodule RabbitMQ.CLI.Diagnostics.Commands.CheckPortConnectivityCommand do
case locals do
[] -> {true, locals}
_ -> check_connectivity_of(locals, node_name, timeout)
_ -> check_connectivity_of(locals, node_name, target_ip, timeout)
end
other ->
@ -64,7 +66,7 @@ defmodule RabbitMQ.CLI.Diagnostics.Commands.CheckPortConnectivityCommand do
{:ok, %{"result" => "ok", "node" => node_name, "listeners" => listener_maps(listeners)}}
end
def output({true, listeners}, %{node: node_name}) do
def output({true, listeners}, %{node: node_name, address: nil}) do
ports =
listeners
|> listener_maps
@ -72,7 +74,20 @@ defmodule RabbitMQ.CLI.Diagnostics.Commands.CheckPortConnectivityCommand do
|> Enum.sort()
|> 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
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 usage, do: "check_port_connectivity"
def usage, do: "check_port_connectivity [--address <target address>]"
def banner([], %{node: node_name}) do
"Testing TCP connections to all active listeners on node #{node_name} ..."
def banner([], %{node: node_name, address: nil}) do
"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
#
# Implementation
#
defp check_connectivity_of(listeners, node_name, timeout) do
defp check_connectivity_of(listeners, node_name, address, timeout) do
# per listener timeout
t = Kernel.trunc(timeout / (length(listeners) + 1))
failures =
Enum.reject(
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

View File

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

View File

@ -20,12 +20,13 @@ defmodule CheckPortConnectivityCommandTest do
{:ok,
opts: %{
node: get_rabbit_hostname(),
address: nil,
timeout: context[:test_timeout] || 30000
}}
end
test "merge_defaults: provides a default timeout" do
assert @command.merge_defaults([], %{}) == {[], %{timeout: 30000}}
assert @command.merge_defaults([], %{}) == {[], %{address: nil, timeout: 30000}}
end
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
assert match?(
{:badrpc, _},
@command.run([], Map.merge(context[:opts], %{node: :jake@thedog}))
@command.run([], Map.merge(context[:opts], %{node: :jake@thedog, address: nil}))
)
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]))
end
@ -53,7 +55,7 @@ defmodule CheckPortConnectivityCommandTest do
end
# 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 =
{:listener, :rabbit@mercurio, :lolz, 'mercurio', {0, 0, 0, 0, 0, 0, 0, 0}, 7_761_613,
[backlog: 128, nodelay: true]}