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