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.Queues.Commands.CheckIfNodeIsMirrorSyncCriticalCommand do
8  @moduledoc """
9  Exits with a non-zero code if there are classic mirrored queues that don't
10  have any in sync mirrors online and would potentially lose data
11  if the target node is shut down.
12
13  This command is meant to be used as a pre-upgrade (pre-shutdown) check.
14  """
15
16  @behaviour RabbitMQ.CLI.CommandBehaviour
17
18  import RabbitMQ.CLI.Core.Platform, only: [line_separator: 0]
19
20  def scopes(), do: [:diagnostics, :queues]
21
22  use RabbitMQ.CLI.Core.AcceptsDefaultSwitchesAndTimeout
23  use RabbitMQ.CLI.Core.MergesNoDefaults
24  use RabbitMQ.CLI.Core.AcceptsNoPositionalArguments
25  use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
26
27  def run([], %{node: node_name, timeout: timeout}) do
28    case :rabbit_misc.rpc_call(node_name,
29           :rabbit_nodes, :is_single_node_cluster, [], timeout) do
30      # if target node is the only one in the cluster, the check makes little sense
31      # and false positives can be misleading
32      true  -> {:ok, :single_node_cluster}
33      false ->
34        case :rabbit_misc.rpc_call(node_name,
35                                    :rabbit_amqqueue, :list_local_mirrored_classic_without_synchronised_mirrors_for_cli, [], timeout) do
36          [] -> {:ok, []}
37          qs when is_list(qs) -> {:ok, qs}
38          other -> other
39        end
40      other -> other
41    end
42  end
43
44  def output({:ok, :single_node_cluster}, %{formatter: "json"}) do
45    {:ok, %{
46      "result"  => "ok",
47      "message" => "Target node seems to be the only one in a single node cluster, the check does not apply"
48    }}
49  end
50  def output({:ok, []}, %{formatter: "json"}) do
51    {:ok, %{"result" => "ok"}}
52  end
53  def output({:ok, :single_node_cluster}, %{silent: true}) do
54    {:ok, :check_passed}
55  end
56  def output({:ok, []}, %{silent: true}) do
57    {:ok, :check_passed}
58  end
59  def output({:ok, :single_node_cluster}, %{node: node_name}) do
60    {:ok, "Node #{node_name} seems to be the only one in a single node cluster, the check does not apply"}
61  end
62  def output({:ok, []}, %{node: node_name}) do
63    {:ok, "Node #{node_name} reported no classic mirrored queues without online synchronised mirrors"}
64  end
65  def output({:ok, qs}, %{node: node_name, formatter: "json"}) when is_list(qs) do
66    {:error, :check_failed,
67     %{
68       "result" => "error",
69       "queues" => qs,
70       "message" => "Node #{node_name} reported local classic mirrored queues without online synchronised mirrors"
71     }}
72  end
73  def output({:ok, qs}, %{silent: true}) when is_list(qs) do
74    {:error, :check_failed}
75  end
76  def output({:ok, qs}, %{node: node_name}) when is_list(qs) do
77    lines = queue_lines(qs, node_name)
78
79    {:error, :check_failed, Enum.join(lines, line_separator())}
80  end
81  use RabbitMQ.CLI.DefaultOutput
82
83  def help_section(), do: :observability_and_health_checks
84
85  def description() do
86    "Health check that exits with a non-zero code if there are classic mirrored queues " <>
87    "without online synchronised mirrors (queues that would potentially lose data if the target node is shut down)"
88  end
89
90  def usage, do: "check_if_node_is_mirror_sync_critical"
91
92  def banner([], %{node: node_name}) do
93    "Checking if node #{node_name} is critical for data safety of any classic mirrored queues ..."
94  end
95
96  #
97  # Implementation
98  #
99
100  def queue_lines(qs, node_name) do
101    for q <- qs do
102      "#{q["readable_name"]} would lose its only synchronised replica (master) if node #{node_name} is stopped"
103    end
104  end
105end
106