2017-07-06 14:47:00 +02:00
|
|
|
%%%----------------------------------------------------------------------
|
2017-07-06 20:27:04 +02:00
|
|
|
%%% File : ejabberd_cluster_mnesia.erl
|
2017-07-06 14:47:00 +02:00
|
|
|
%%% Author : Christophe Romain <christophe.romain@process-one.net>
|
2017-07-06 20:27:04 +02:00
|
|
|
%%% Purpose : Ejabberd clustering management via Mnesia
|
2017-07-06 14:47:00 +02:00
|
|
|
%%% Created : 7 Oct 2015 by Christophe Romain <christophe.romain@process-one.net>
|
|
|
|
%%%
|
|
|
|
%%%
|
2019-01-08 22:53:27 +01:00
|
|
|
%%% ejabberd, Copyright (C) 2002-2019 ProcessOne
|
2017-07-06 14:47:00 +02:00
|
|
|
%%%
|
|
|
|
%%% This program is free software; you can redistribute it and/or
|
|
|
|
%%% modify it under the terms of the GNU General Public License as
|
|
|
|
%%% published by the Free Software Foundation; either version 2 of the
|
|
|
|
%%% License, or (at your option) any later version.
|
|
|
|
%%%
|
|
|
|
%%% This program is distributed in the hope that it will be useful,
|
|
|
|
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
%%% General Public License for more details.
|
|
|
|
%%%
|
|
|
|
%%% You should have received a copy of the GNU General Public License along
|
|
|
|
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
|
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
%%%
|
|
|
|
%%%----------------------------------------------------------------------
|
|
|
|
|
|
|
|
-module(ejabberd_cluster_mnesia).
|
|
|
|
-behaviour(ejabberd_cluster).
|
|
|
|
|
|
|
|
%% API
|
|
|
|
-export([init/0, get_nodes/0, join/1, leave/1,
|
|
|
|
get_known_nodes/0, node_id/0, get_node_by_id/1,
|
|
|
|
send/2, wait_for_sync/1, subscribe/1]).
|
|
|
|
|
|
|
|
-include("logger.hrl").
|
|
|
|
|
|
|
|
-spec init() -> ok.
|
|
|
|
init() ->
|
|
|
|
ok.
|
|
|
|
|
|
|
|
-spec get_nodes() -> [node()].
|
|
|
|
|
|
|
|
get_nodes() ->
|
|
|
|
mnesia:system_info(running_db_nodes).
|
|
|
|
|
|
|
|
-spec get_known_nodes() -> [node()].
|
|
|
|
|
|
|
|
get_known_nodes() ->
|
|
|
|
lists:usort(mnesia:system_info(db_nodes)
|
|
|
|
++ mnesia:system_info(extra_db_nodes)).
|
|
|
|
|
|
|
|
-spec join(node()) -> ok | {error, any()}.
|
|
|
|
|
|
|
|
join(Node) ->
|
|
|
|
case {node(), net_adm:ping(Node)} of
|
|
|
|
{Node, _} ->
|
|
|
|
{error, {not_master, Node}};
|
|
|
|
{_, pong} ->
|
|
|
|
application:stop(ejabberd),
|
|
|
|
application:stop(mnesia),
|
|
|
|
mnesia:delete_schema([node()]),
|
|
|
|
application:start(mnesia),
|
2017-12-21 14:07:29 +01:00
|
|
|
case mnesia:change_config(extra_db_nodes, [Node]) of
|
|
|
|
{ok, _} ->
|
|
|
|
replicate_database(Node),
|
|
|
|
wait_for_sync(infinity),
|
|
|
|
application:stop(mnesia),
|
|
|
|
application:start(ejabberd);
|
|
|
|
{error, Reason} ->
|
|
|
|
{error, Reason}
|
|
|
|
end;
|
2017-07-06 14:47:00 +02:00
|
|
|
_ ->
|
|
|
|
{error, {no_ping, Node}}
|
|
|
|
end.
|
|
|
|
|
|
|
|
-spec leave(node()) -> ok | {error, any()}.
|
|
|
|
|
|
|
|
leave(Node) ->
|
|
|
|
case {node(), net_adm:ping(Node)} of
|
|
|
|
{Node, _} ->
|
|
|
|
Cluster = get_nodes()--[Node],
|
|
|
|
leave(Cluster, Node);
|
|
|
|
{_, pong} ->
|
|
|
|
rpc:call(Node, ?MODULE, leave, [Node], 10000);
|
|
|
|
{_, pang} ->
|
|
|
|
case mnesia:del_table_copy(schema, Node) of
|
|
|
|
{atomic, ok} -> ok;
|
|
|
|
{aborted, Reason} -> {error, Reason}
|
|
|
|
end
|
|
|
|
end.
|
|
|
|
leave([], Node) ->
|
|
|
|
{error, {no_cluster, Node}};
|
|
|
|
leave([Master|_], Node) ->
|
|
|
|
application:stop(ejabberd),
|
|
|
|
application:stop(mnesia),
|
|
|
|
spawn(fun() ->
|
2017-12-21 14:07:29 +01:00
|
|
|
rpc:call(Master, mnesia, del_table_copy, [schema, Node]),
|
|
|
|
mnesia:delete_schema([node()]),
|
|
|
|
erlang:halt(0)
|
|
|
|
end),
|
2017-07-06 14:47:00 +02:00
|
|
|
ok.
|
|
|
|
|
|
|
|
-spec node_id() -> binary().
|
|
|
|
node_id() ->
|
|
|
|
integer_to_binary(erlang:phash2(node())).
|
|
|
|
|
|
|
|
-spec get_node_by_id(binary()) -> node().
|
|
|
|
get_node_by_id(Hash) ->
|
|
|
|
try binary_to_integer(Hash) of
|
|
|
|
I -> match_node_id(I)
|
|
|
|
catch _:_ ->
|
|
|
|
node()
|
|
|
|
end.
|
|
|
|
|
|
|
|
-spec send({atom(), node()}, term()) -> boolean().
|
|
|
|
send(Dst, Msg) ->
|
2018-01-05 10:15:28 +01:00
|
|
|
case erlang:send(Dst, Msg, [nosuspend, noconnect]) of
|
|
|
|
ok -> true;
|
|
|
|
_ -> false
|
|
|
|
end.
|
2017-07-06 14:47:00 +02:00
|
|
|
|
|
|
|
-spec wait_for_sync(timeout()) -> ok.
|
|
|
|
wait_for_sync(Timeout) ->
|
|
|
|
?INFO_MSG("Waiting for Mnesia synchronization to complete", []),
|
|
|
|
mnesia:wait_for_tables(mnesia:system_info(local_tables), Timeout),
|
|
|
|
ok.
|
|
|
|
|
|
|
|
-spec subscribe(_) -> ok.
|
|
|
|
subscribe(_) ->
|
|
|
|
ok.
|
|
|
|
|
|
|
|
%%%===================================================================
|
|
|
|
%%% Internal functions
|
|
|
|
%%%===================================================================
|
2017-12-21 14:07:29 +01:00
|
|
|
|
|
|
|
replicate_database(Node) ->
|
|
|
|
mnesia:change_table_copy_type(schema, node(), disc_copies),
|
|
|
|
lists:foreach(
|
|
|
|
fun(Table) ->
|
2019-07-31 18:25:22 +02:00
|
|
|
Type = rpc:call(Node, mnesia, table_info, [Table, storage_type]),
|
2017-12-21 14:07:29 +01:00
|
|
|
mnesia:add_table_copy(Table, node(), Type)
|
|
|
|
end, mnesia:system_info(tables)--[schema]).
|
|
|
|
|
2017-07-06 14:47:00 +02:00
|
|
|
-spec match_node_id(integer()) -> node().
|
|
|
|
match_node_id(I) ->
|
|
|
|
match_node_id(I, get_nodes()).
|
|
|
|
|
|
|
|
-spec match_node_id(integer(), [node()]) -> node().
|
|
|
|
match_node_id(I, [Node|Nodes]) ->
|
|
|
|
case erlang:phash2(Node) of
|
|
|
|
I -> Node;
|
|
|
|
_ -> match_node_id(I, Nodes)
|
|
|
|
end;
|
|
|
|
match_node_id(_I, []) ->
|
|
|
|
node().
|