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-2021 VMware, Inc. or its affiliates. All rights reserved. 6 7defmodule RabbitMQ.CLI.Diagnostics.Commands.CheckPortConnectivityCommand do 8 @moduledoc """ 9 Checks all listeners on the target node by opening a TCP connection to each 10 and immediately closing it. 11 12 Returns a code of 0 unless there were connectivity and authentication 13 errors. This command is meant to be used in health checks. 14 """ 15 16 import RabbitMQ.CLI.Diagnostics.Helpers, 17 only: [check_listener_connectivity: 3] 18 import RabbitMQ.CLI.Core.Platform, only: [line_separator: 0] 19 import RabbitMQ.CLI.Core.Listeners 20 21 @behaviour RabbitMQ.CLI.CommandBehaviour 22 23 @default_timeout 30_000 24 25 use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout 26 27 def merge_defaults(args, opts) do 28 timeout = 29 case opts[:timeout] do 30 nil -> @default_timeout 31 :infinity -> @default_timeout 32 other -> other 33 end 34 35 {args, Map.merge(opts, %{timeout: timeout})} 36 end 37 38 use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments 39 use RabbitMQ.CLI.Core.RequiresRabbitAppRunning 40 41 def run([], %{node: node_name, timeout: timeout}) do 42 case :rabbit_misc.rpc_call(node_name, :rabbit_networking, :active_listeners, [], timeout) do 43 {:error, _} = err -> 44 err 45 46 {:error, _, _} = err -> 47 err 48 49 xs when is_list(xs) -> 50 locals = listeners_on(xs, node_name) 51 52 case locals do 53 [] -> {true, locals} 54 _ -> check_connectivity_of(locals, node_name, timeout) 55 end 56 57 other -> 58 other 59 end 60 end 61 62 def output({true, listeners}, %{node: node_name, formatter: "json"}) do 63 {:ok, %{"result" => "ok", "node" => node_name, "listeners" => listener_maps(listeners)}} 64 end 65 66 def output({true, listeners}, %{node: node_name}) do 67 ports = 68 listeners 69 |> listener_maps 70 |> Enum.map(fn %{port: p} -> p end) 71 |> Enum.sort() 72 |> Enum.join(", ") 73 74 {:ok, "Successfully connected to ports #{ports} on node #{node_name}."} 75 end 76 77 def output({false, failures}, %{formatter: "json", node: node_name}) do 78 {:error, %{"result" => "error", "node" => node_name, "failures" => listener_maps(failures)}} 79 end 80 81 def output({false, failures}, %{node: node_name}) do 82 lines = [ 83 "Connection to ports of the following listeners on node #{node_name} failed: " 84 | listener_lines(failures) 85 ] 86 87 {:error, Enum.join(lines, line_separator())} 88 end 89 90 def description(), do: "Basic TCP connectivity health check for each listener's port on the target node" 91 92 def help_section(), do: :observability_and_health_checks 93 94 def usage, do: "check_port_connectivity" 95 96 def banner([], %{node: node_name}) do 97 "Testing TCP connections to all active listeners on node #{node_name} ..." 98 end 99 100 # 101 # Implementation 102 # 103 104 defp check_connectivity_of(listeners, node_name, timeout) do 105 # per listener timeout 106 t = Kernel.trunc(timeout / (length(listeners) + 1)) 107 108 failures = 109 Enum.reject( 110 listeners, 111 fn l -> check_listener_connectivity(listener_map(l), node_name, t) end 112 ) 113 114 case failures do 115 [] -> {true, listeners} 116 fs -> {false, fs} 117 end 118 end 119end 120