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