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