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) 2010-2021 VMware, Inc. or its affiliates.  All rights reserved.
6%%
7
8-module(rabbit_mirror_queue_mode_nodes).
9
10-include_lib("rabbit_common/include/rabbit.hrl").
11
12-behaviour(rabbit_mirror_queue_mode).
13
14-export([description/0, suggested_queue_nodes/5, validate_policy/1]).
15
16-rabbit_boot_step({?MODULE,
17                   [{description, "mirror mode nodes"},
18                    {mfa,         {rabbit_registry, register,
19                                   [ha_mode, <<"nodes">>, ?MODULE]}},
20                    {requires,    rabbit_registry},
21                    {enables,     kernel_ready}]}).
22
23description() ->
24    [{description, <<"Mirror queue to specified nodes">>}].
25
26suggested_queue_nodes(PolicyNodes0, CurrentMaster, _SNodes, SSNodes, NodesRunningRabbitMQ) ->
27    PolicyNodes1 = [list_to_atom(binary_to_list(Node)) || Node <- PolicyNodes0],
28    %% If the current master is not in the nodes specified, then what we want
29    %% to do depends on whether there are any synchronised mirrors. If there
30    %% are then we can just kill the current master - the admin has asked for
31    %% a migration and we should give it to them. If there are not however
32    %% then we must keep the master around so as not to lose messages.
33
34    PolicyNodes = case SSNodes of
35                      [] -> lists:usort([CurrentMaster | PolicyNodes1]);
36                      _  -> PolicyNodes1
37                  end,
38    Unavailable = PolicyNodes -- NodesRunningRabbitMQ,
39    AvailablePolicyNodes = PolicyNodes -- Unavailable,
40    case AvailablePolicyNodes of
41        [] -> %% We have never heard of anything? Not much we can do but
42              %% keep the master alive.
43              {CurrentMaster, []};
44        _  -> case lists:member(CurrentMaster, AvailablePolicyNodes) of
45                  true  -> {CurrentMaster,
46                            AvailablePolicyNodes -- [CurrentMaster]};
47                  false -> %% Make sure the new master is synced! In order to
48                           %% get here SSNodes must not be empty.
49                           SyncPolicyNodes = [Node ||
50                                              Node <- AvailablePolicyNodes,
51                                              lists:member(Node, SSNodes)],
52                           NewMaster = case SyncPolicyNodes of
53                                          [Node | _] -> Node;
54                                          []         -> erlang:hd(SSNodes)
55                                      end,
56                           {NewMaster, AvailablePolicyNodes -- [NewMaster]}
57              end
58    end.
59
60validate_policy([]) ->
61    {error, "ha-mode=\"nodes\" list must be non-empty", []};
62validate_policy(Nodes) when is_list(Nodes) ->
63    case [I || I <- Nodes, not is_binary(I)] of
64        []      -> ok;
65        Invalid -> {error, "ha-mode=\"nodes\" takes a list of strings, "
66                    "~p was not a string", [Invalid]}
67    end;
68validate_policy(Params) ->
69    {error, "ha-mode=\"nodes\" takes a list, ~p given", [Params]}.
70