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%%
7
8-module(rabbit_peer_discovery).
9
10%%
11%% API
12%%
13
14-export([maybe_init/0, discover_cluster_nodes/0, backend/0, node_type/0,
15         normalize/1, format_discovered_nodes/1, log_configured_backend/0,
16         register/0, unregister/0, maybe_register/0, maybe_unregister/0,
17         lock/0, unlock/1, discovery_retries/0]).
18-export([append_node_prefix/1, node_prefix/0, locking_retry_timeout/0,
19         lock_acquisition_failure_mode/0]).
20
21-define(DEFAULT_BACKEND,   rabbit_peer_discovery_classic_config).
22
23%% what node type is used by default for this node when joining
24%% a new cluster as a virgin node
25-define(DEFAULT_NODE_TYPE, disc).
26
27%% default node prefix to attach to discovered hostnames
28-define(DEFAULT_PREFIX, "rabbit").
29
30%% default discovery retries and interval.
31-define(DEFAULT_DISCOVERY_RETRY_COUNT, 10).
32-define(DEFAULT_DISCOVERY_RETRY_INTERVAL_MS, 500).
33
34-define(NODENAME_PART_SEPARATOR, "@").
35
36-spec backend() -> atom().
37
38backend() ->
39  case application:get_env(rabbit, cluster_formation) of
40    {ok, Proplist} ->
41      proplists:get_value(peer_discovery_backend, Proplist, ?DEFAULT_BACKEND);
42    undefined      ->
43      ?DEFAULT_BACKEND
44  end.
45
46
47
48-spec node_type() -> rabbit_types:node_type().
49
50node_type() ->
51  case application:get_env(rabbit, cluster_formation) of
52    {ok, Proplist} ->
53      proplists:get_value(node_type, Proplist, ?DEFAULT_NODE_TYPE);
54    undefined      ->
55      ?DEFAULT_NODE_TYPE
56  end.
57
58-spec locking_retry_timeout() -> {Retries :: integer(), Timeout :: integer()}.
59
60locking_retry_timeout() ->
61    case application:get_env(rabbit, cluster_formation) of
62        {ok, Proplist} ->
63            Retries = proplists:get_value(lock_retry_limit, Proplist, 10),
64            Timeout = proplists:get_value(lock_retry_timeout, Proplist, 30000),
65            {Retries, Timeout};
66        undefined ->
67            {10, 30000}
68    end.
69
70-spec lock_acquisition_failure_mode() -> ignore | fail.
71
72lock_acquisition_failure_mode() ->
73    case application:get_env(rabbit, cluster_formation) of
74        {ok, Proplist} ->
75            proplists:get_value(lock_acquisition_failure_mode, Proplist, fail);
76        undefined      ->
77            fail
78  end.
79
80-spec log_configured_backend() -> ok.
81
82log_configured_backend() ->
83  rabbit_log:info("Configured peer discovery backend: ~s", [backend()]).
84
85maybe_init() ->
86    Backend = backend(),
87    code:ensure_loaded(Backend),
88    case erlang:function_exported(Backend, init, 0) of
89        true  ->
90            rabbit_log:debug("Peer discovery backend supports initialisation"),
91            case Backend:init() of
92                ok ->
93                    rabbit_log:debug("Peer discovery backend initialisation succeeded"),
94                    ok;
95                {error, Error} ->
96                    rabbit_log:warning("Peer discovery backend initialisation failed: ~p.", [Error]),
97                    ok
98            end;
99        false ->
100            rabbit_log:debug("Peer discovery backend does not support initialisation"),
101            ok
102    end.
103
104
105%% This module doesn't currently sanity-check the return value of
106%% `Backend:list_nodes()`. Therefore, it could return something invalid:
107%% thus the `{œk, any()} in the spec.
108%%
109%% `rabbit_mnesia:init_from_config()` does some verifications.
110
111-spec discover_cluster_nodes() ->
112    {ok, {Nodes :: [node()], NodeType :: rabbit_types:node_type()} | any()} |
113    {error, Reason :: string()}.
114
115discover_cluster_nodes() ->
116    Backend = backend(),
117    normalize(Backend:list_nodes()).
118
119
120-spec maybe_register() -> ok.
121
122maybe_register() ->
123  Backend = backend(),
124  case Backend:supports_registration() of
125    true  ->
126      register(),
127      Backend:post_registration();
128    false ->
129      rabbit_log:info("Peer discovery backend ~s does not support registration, skipping registration.", [Backend]),
130      ok
131  end.
132
133
134-spec maybe_unregister() -> ok.
135
136maybe_unregister() ->
137  Backend = backend(),
138  case Backend:supports_registration() of
139    true  ->
140      unregister();
141    false ->
142      rabbit_log:info("Peer discovery backend ~s does not support registration, skipping unregistration.", [Backend]),
143      ok
144  end.
145
146-spec discovery_retries() -> {Retries :: integer(), Interval :: integer()}.
147
148discovery_retries() ->
149    case application:get_env(rabbit, cluster_formation) of
150        {ok, Proplist} ->
151            Retries  = proplists:get_value(discovery_retry_limit,    Proplist, ?DEFAULT_DISCOVERY_RETRY_COUNT),
152            Interval = proplists:get_value(discovery_retry_interval, Proplist, ?DEFAULT_DISCOVERY_RETRY_INTERVAL_MS),
153            {Retries, Interval};
154        undefined ->
155            {?DEFAULT_DISCOVERY_RETRY_COUNT, ?DEFAULT_DISCOVERY_RETRY_INTERVAL_MS}
156    end.
157
158-spec register() -> ok.
159
160register() ->
161  Backend = backend(),
162  rabbit_log:info("Will register with peer discovery backend ~s", [Backend]),
163  case Backend:register() of
164    ok             -> ok;
165    {error, Error} ->
166      rabbit_log:error("Failed to register with peer discovery backend ~s: ~p",
167        [Backend, Error]),
168      ok
169  end.
170
171
172-spec unregister() -> ok.
173
174unregister() ->
175  Backend = backend(),
176  rabbit_log:info("Will unregister with peer discovery backend ~s", [Backend]),
177  case Backend:unregister() of
178    ok             -> ok;
179    {error, Error} ->
180      rabbit_log:error("Failed to unregister with peer discovery backend ~s: ~p",
181        [Backend, Error]),
182      ok
183  end.
184
185-spec lock() -> {ok, Data :: term()} | not_supported | {error, Reason :: string()}.
186
187lock() ->
188    Backend = backend(),
189    rabbit_log:info("Will try to lock with peer discovery backend ~s", [Backend]),
190    case Backend:lock(node()) of
191        {error, Reason} = Error ->
192            rabbit_log:error("Failed to lock with peer discovery backend ~s: ~p",
193                             [Backend, Reason]),
194            Error;
195        Any ->
196            Any
197    end.
198
199-spec unlock(Data :: term()) -> ok | {error, Reason :: string()}.
200
201unlock(Data) ->
202    Backend = backend(),
203    rabbit_log:info("Will try to unlock with peer discovery backend ~s", [Backend]),
204    case Backend:unlock(Data) of
205        {error, Reason} = Error ->
206            rabbit_log:error("Failed to unlock with peer discovery backend ~s: ~p, "
207                             "lock data: ~p",
208                             [Backend, Reason, Data]),
209            Error;
210        Any ->
211            Any
212    end.
213
214%%
215%% Implementation
216%%
217
218-spec normalize(Nodes :: [node()] |
219                {Nodes :: [node()],
220                 NodeType :: rabbit_types:node_type()} |
221                {ok, Nodes :: [node()]} |
222                {ok, {Nodes :: [node()],
223                      NodeType :: rabbit_types:node_type()}} |
224                {error, Reason :: string()}) ->
225    {ok, {Nodes :: [node()], NodeType :: rabbit_types:node_type()}} |
226    {error, Reason :: string()}.
227
228normalize(Nodes) when is_list(Nodes) ->
229  {ok, {Nodes, disc}};
230normalize({Nodes, NodeType}) when is_list(Nodes) andalso is_atom(NodeType) ->
231  {ok, {Nodes, NodeType}};
232normalize({ok, Nodes}) when is_list(Nodes) ->
233  {ok, {Nodes, disc}};
234normalize({ok, {Nodes, NodeType}}) when is_list(Nodes) andalso is_atom(NodeType) ->
235  {ok, {Nodes, NodeType}};
236normalize({error, Reason}) ->
237  {error, Reason}.
238
239-spec format_discovered_nodes(Nodes :: list()) -> string().
240
241format_discovered_nodes(Nodes) ->
242  %% NOTE: in OTP 21 string:join/2 is deprecated but still available.
243  %%       Its recommended replacement is not a drop-in one, though, so
244  %%       we will not be switching just yet.
245  string:join(lists:map(fun rabbit_data_coercion:to_list/1, Nodes), ", ").
246
247
248
249-spec node_prefix() -> string().
250
251node_prefix() ->
252    case string:tokens(atom_to_list(node()), ?NODENAME_PART_SEPARATOR) of
253        [Prefix, _] -> Prefix;
254        [_]         -> ?DEFAULT_PREFIX
255    end.
256
257
258
259-spec append_node_prefix(Value :: binary() | string()) -> string().
260
261append_node_prefix(Value) when is_binary(Value) orelse is_list(Value) ->
262    Val = rabbit_data_coercion:to_list(Value),
263    Hostname = case string:tokens(Val, ?NODENAME_PART_SEPARATOR) of
264                   [_ExistingPrefix, HN] -> HN;
265                   [HN]                  -> HN
266               end,
267    string:join([node_prefix(), Hostname], ?NODENAME_PART_SEPARATOR).
268