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.CheckPortListenerCommand do
8  @moduledoc """
9  Exits with a non-zero code if there is no active listener
10  for the given port on the target node.
11
12  This command is meant to be used in health checks.
13  """
14
15  import RabbitMQ.CLI.Core.Listeners, only: [listeners_on: 2, listener_maps: 1]
16
17  @behaviour RabbitMQ.CLI.CommandBehaviour
18
19  use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
20  use RabbitMQ.CLI.Core.MergesNoDefaults
21  use RabbitMQ.CLI.Core.AcceptsOnePositiveIntegerArgument
22  use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
23
24  def run([port], %{node: node_name, timeout: timeout}) do
25    case :rabbit_misc.rpc_call(node_name, :rabbit_networking, :active_listeners, [], timeout) do
26      {:error, _} = err ->
27        err
28
29      {:error, _, _} = err ->
30        err
31
32      xs when is_list(xs) ->
33        locals = listeners_on(xs, node_name) |> listener_maps
34
35        found =
36          Enum.any?(locals, fn %{port: p} ->
37            to_string(port) == to_string(p)
38          end)
39
40        case found do
41          true -> {true, port}
42          false -> {false, port, locals}
43        end
44
45      other ->
46        other
47    end
48  end
49
50  def output({true, port}, %{node: node_name, formatter: "json"}) do
51    {:ok, %{"result" => "ok", "node" => node_name, "port" => port}}
52  end
53
54  def output({true, port}, %{node: node_name}) do
55    {:ok, "A listener for port #{port} is running on node #{node_name}."}
56  end
57
58  def output({false, port, listeners}, %{formatter: "json"}) do
59    ports = Enum.map(listeners, fn %{port: p} -> p end)
60
61    {:error, :check_failed,
62     %{"result" => "error", "missing" => port, "ports" => ports, "listeners" => listeners}}
63  end
64
65  def output({false, port, listeners}, %{node: node_name}) do
66    ports = Enum.map(listeners, fn %{port: p} -> p end) |> Enum.sort() |> Enum.join(", ")
67
68    {:error,  :check_failed,
69     "No listener for port #{port} is active on node #{node_name}. " <>
70       "Found listeners that use the following ports: #{ports}"}
71  end
72
73  def help_section(), do: :observability_and_health_checks
74
75  def description(), do: "Health check that exits with a non-zero code if target node does not have an active listener for given port"
76
77  def usage, do: "check_port_listener <port>"
78
79  def banner([port], %{node: node_name}) do
80    "Asking node #{node_name} if there's an active listener on port #{port} ..."
81  end
82end
83